【发布时间】:2013-09-30 06:20:19
【问题描述】:
我有一个关于类成员函数的问题。这涉及继承。我在下面写了以下代码,但我并不太明白它是如何工作的(我只能猜测):
#include <iostream>
using namespace std;
class Base
{
};
typedef void(Base::*handler)();
#define selector(_SELECTOR) static_cast<handler>(&_SELECTOR)
class Boo : public Base
{
public:
void callingFunc()
{
cout << "Hey there" << endl;
}
};
class Foo
{
public:
void setCallback( Base * instance, void (Base::*funcToCall)(void) )
{
this->instance = instance;
this->funcToCall = funcToCall;
}
void doCall()
{
(instance->*funcToCall)();
}
private:
Base* instance;
void (Base::*funcToCall)(void);
};
void main()
{
Foo * foo = new Foo();
Boo * boo = new Boo();
foo->setCallback(boo, selector(Boo::callingFunc) );
foo->doCall(); // outputs "Hey there"
}
此代码有效,但我想知道详细原因。 doCall() 似乎将 funcToCall 从 (void (Base::*)(void)) 向上转换为 (void (Boo::*)(void)) 类型。它似乎也将我的instance 变量转换为Boo!它似乎神奇地知道我给的funcToCall 属于Boo 并相应地转换。
这是怎么做到的?这是在运行时还是编译时完成的?当我调用(instance->*funcToCall)(); 时,它是否只是尝试查找函数的名称?
不要对typedef 吹毛求疵。我知道有些东西需要typedef 才能阅读。这只是测试代码。
编辑:我更多地使用了代码,但它看起来很陌生。我添加了一个新类,结果如下:
class Goo : public Base
{
public:
void callingFunc()
{
cout << "Yo there" << endl;
}
};
void main()
{
Foo * foo = new Foo();
Boo * boo = new Boo();
Goo * goo = new Goo();
foo->setCallback(goo, selector(Boo::callingFunc) );
foo->doCall(); // outputs "Hey there" not "Yo there"
}
在这一点上,这有点道理,但也有点没有意义。我的意思是,很明显它会从 Boo 中调用“Hey There”,但为什么代码不会爆炸?看起来很危险。
编辑 2: 发现了一些真正令人不安和令人不安的事情。我调整了代码,所以接受一个计数器,这样我就有一个变量来检查正在发生的事情。
#include <iostream>
using namespace std;
class Base
{
};
typedef void(Base::*handler)();
#define selector(_SELECTOR) static_cast<handler>(&_SELECTOR)
class Boo : public Base
{
public:
Boo() : counter(0) {}
void callingFunc()
{
cout << "Hey there " << counter << " at " << &counter << endl;
counter += 1;
}
int counter;
};
class Goo : public Base
{
public:
Goo() : counter(0) {}
void callingFunc()
{
cout << "Yo there " << counter << " at " << &counter << endl;
counter += 1;
}
int counter;
};
class Foo
{
public:
void setCallback( Base * instance, void (Base::*funcToCall)(void) )
{
this->instance = instance;
this->funcToCall = funcToCall;
}
void doCall()
{
(instance->*funcToCall)();
}
private:
Base* instance;
void (Base::*funcToCall)(void);
};
void main()
{
Foo * foo = new Foo();
Boo * boo = new Boo();
Base * base = new Base();
Goo * goo = new Goo();
// first run
foo->setCallback(goo, selector(Boo::callingFunc) );
foo->doCall(); // "Hey there 0 at 0044BC60"
foo->setCallback(boo, selector(Boo::callingFunc) );
foo->doCall(); // "Hey there 0 at 0044BC00"
//second run
foo->setCallback(goo, selector(Boo::callingFunc) );
foo->doCall(); // "Hey there 1 at 0044BC60"
foo->setCallback(boo, selector(Boo::callingFunc) );
foo->doCall(); // "Hey there 1 at 0044BC00"
// attempt with base
foo->setCallback(base, selector(Boo::callingFunc) );
foo->doCall(); // "Hey there *rubbish number* at at 0044BC30"
}
现在我很确定函数回调是一个运行时的东西(显然是因为它没有给出编译错误,但我不确定,因为通常情况并非如此)。如果它是运行时的东西,那么它是有道理的,因为它几乎像脚本语言一样工作(按名称查找变量,如果存在则更新它等)。
我仍然需要有人来确认这一点。它确实看起来既强大又危险。我真的有一段时间没有看到这样的东西了。我现在太忙了,无法尝试打开组装中的东西来破译到底发生了什么。另外,我不擅长阅读它^^;;
EDIT 3 谢谢大家,现在一切都说得通了。 villekulla 的回答让我相信,因为我的 Boo 和 Goo 类的结构相同,所以它能够以相同的方式访问“计数器”变量(如果您了解类和结构的内存是如何分配的,这应该很明显)。所以我在 Goo 插入了一个 'char' 变量:
class Goo : public Base
{
public:
Goo() : counter(0) {}
void callingFunc()
{
cout << "Yo there " << counter << " at " << &counter << endl;
counter += 1;
}
char hey;
int counter;
};
调用:
foo->setCallback(goo, selector(Boo::callingFunc) );
foo->doCall();
两次会产生乱码,因为它正在抓取计数器应该在的字符(确认未定义的行为,如所述)。没有编译错误是因为……嗯……就编译而言,代码没有什么大错。
再次感谢!
【问题讨论】:
标签: c++ function class pointers member