修改
在下面的代码中,container::push
将T
类型的对象从base
派生为参数,并在向量中存储指向方法bool T::test()
的指针。
container::call
调用成员对象p
,的上下文中的每个存储方法,其类型为base
,而不是T
。只要被调用的方法没有引用base
之外的任何成员并且test()
未被声明为virtual
,它就会起作用。
我知道这很难看,甚至可能都不正确。
如何以更好的方式完成同样的事情?
#include <iostream>
#include <tr1/functional>
#include <vector>
class base {
public:
base(int v) : x(v)
{}
bool test() const { // this is NOT called
return false;
}
protected:
int x;
};
class derived : public base {
public:
bool test() const { // this is called instead
return (x == 42);
}
};
class container {
public:
container() : p(42)
{}
template<typename T>
void push(const T&) {
vec.push_back((bool (base::*)() const) &T::test);
}
void call() {
std::vector<bool (base::*)() const>::iterator i;
for(i = vec.begin(); i != vec.end(); ++i) {
if( (p .* (*i))() ) {
std::cout << "ok\n";
}
}
}
private:
std::vector<bool (base::*)() const> vec;
base p;
};
int main(int argc, char* argv[]) {
container c;
c.push(derived());
c.call();
return 0;
}
答案 0 :(得分:3)
使用“boost :: bind”语句执行的操作是调用derived :: test并将“b”作为“this”指针传递。重要的是要记住derived :: test的“this”指针应该是指向“派生”对象的指针 - 对你来说情况并非如此。它适用于您的特定情况,因为您没有vtable并且内存布局相同 - 但只要更改,您的程序可能会中断。
此外,它只是完全错误 - 丑陋,难以理解,容易出错的代码。你真的想做什么?
[编辑]编辑问题的新答案:你应该使用boost :: bind来创建一个功能闭包,它包装了对象&amp;单个对象中的成员函数 - 并将该对象存储在集合中。然后当你调用它时,它总是可靠的。 如果你不能在你的应用程序中使用boost ......好吧,你可以做一些像boost :: bind自己的东西(看看它是如何在boost中完成的),但你更有可能会弄错并且有错误。
答案 1 :(得分:1)
你正在做的事情是不正确的,并且在简单的例子中它将起作用,但在其他情况下可能只会引起地狱(未定义行为的可能性之一)。
由于base::test
和derived::test
不是虚拟的,因此它们是两种不同的成员方法,所以我会简单地将它们称为base::foo
和derived::bar
。在绑定器代码中,您强制编译器调整指向bar
中定义的derived
的指针,就像它在base
中实际定义然后调用它一样。也就是说,您在对象上调用derived
的方法或键入base
!这是未定义的行为。
它不会死的原因是this
和base
中的derived
指针重合,并且您只访问base
类中的数据。但这是不正确的。
当您声明base::test
虚拟时,您会得到正确的行为:层次结构中最派生的对象是base
,编译器将使用虚拟调度机制并找出base
是找到并执行test
的最终覆盖的地方。
当您仅将derived::test
声明为虚拟(而不是base
)时,编译器将尝试在已处理对象中使用不存在的虚拟分派机制(通常是vtable指针)并杀死应用程序。
无论如何,虚拟base::test
使用的所有内容均不正确。根据您的实际要求,最可能的正确方法是:
class base {
public:
virtual bool test() const;
};
class derived : public base {
public:
virtual bool test() const; // <--- virtual is optional here, but informative
};
int main()
{
derived d; // <--- the actual final type
base & b = d; // <--- optional
if ( std::tr1::bind( &base::test, std::tr1::ref(b))() ) {
// ...
}
}
请注意,没有强制转换(强制转换通常暗示某些奇怪的东西,有可能隐藏在那里),对象属于您希望调用该方法的具体类型,并且虚拟调度机制保证即使是
绑定到base::test
,因为方法是虚拟的,最终的覆盖将被执行。
这另一个例子更有可能做有趣的事情(我还没试过):
struct base {
void foo() {}
};
struct derived : base {
void foo() {
for ( int i = 0; i < 1000; ++i ) {
std::cout << data[i];
}
}
int data[1000];
};
int main() {
base b;
std::tr1::bind((void (base::*)()) &derived::foo, std::tr1::ref(b))();
}
答案 2 :(得分:1)
更新问题:
在基础对象上调用派生成员函数是Undefined Behavior。你想要实现的(代码)是错误的。尝试发布您需要的内容,人们将通过合理的设计提供帮助。