我有一个关于类成员函数的问题。这涉及继承。我在下面编写了以下代码,但我并不真正了解它是如何工作的(我只能猜测):
#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 (Boo::*)(void))
(void (Base::*)(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"
}
现在我很确定函数回调是一个运行时的东西(显然是因为它没有给出编译错误,但我不确定,因为通常情况并非如此)。如果它是运行时的东西,那么它是有道理的,因为它几乎像脚本语言一样工作(按名称查找变量,如果存在则更新它等)。
我仍然需要有人来确认这一点。它确实看起来既强大又危险。我真的有一段时间没有看到这样的东西了。我现在太忙了,无法尝试打开组装中的东西来破译到底发生了什么。另外,我不擅长阅读它^^;;
编辑 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();
两次会产生胡言乱语,因为它正在抓取计数器应该在的字符(确认如上所述的未定义行为)。没有编译错误,因为……嗯……就编译而言,代码没有什么大错。
再次感谢!