我有一个关于类成员函数的问题。这涉及继承。我在下面写了下面的代码,但我并不真正理解它是如何工作的(我只能猜测):
#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 (基地::*)(无效))
。它还似乎将我的 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();
twice 会产生乱码,因为它正在抓取计数器应该在的字符(确认未定义的行为,如前所述)。没有编译错误,因为......好吧......就编译而言,代码没有任何严重错误。
再次感谢!
最佳答案
这是未定义的行为。
考虑向 Foo 和 Goo 添加一个成员并调整 callingFunc 以使用该成员:
class Boo : public Base
{
public:
Boo()
: m("Boo")
{}
void callingFunc()
{
cout << "Hey there, I'am " << boo << endl;
}
const char* m;
};
class Goo : public Base
{
public:
Goo()
: m("Goo")
{}
void callingFunc()
{
cout << "Yo there, I'am " << goo << endl;
}
const char* m;
};
在案例中
foo->setCallback(boo, selector(Boo::callingFunc) );
你得到输出
Hey there, I'am Boo
在这种情况下
foo->setCallback(goo, selector(Boo::callingFunc) );
你得到输出
Hey there, I'am Goo
你清楚地看到 Boo::callingFunc 得到了 Goo 的一些实例......
这是 C++ 搬起石头砸自己脚的数百万个例子之一……只做标准允许的事情:/
你的例子并没有爆炸,因为 callingFunc 和 Goo/Foo 是微不足道的。如果你不幸它永远不会爆炸,它“只会”引入奇怪的错误(Foo::callingFunc 处理 Goo::callingFunc 的数据)。
因为您没有使用虚函数,所有函数调用地址都在编译时获取地址时解析(在 foo->setCallback(boo, selector(Boo::callingFunc) 行);
关于c++ - C++如何识别使用哪个函数(类成员函数指针相关),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19087588/