C ++:绑定到基类

时间:2010-04-16 08:47:44

标签: c++ boost bind

修改

在下面的代码中,container::pushT类型的对象从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;
}

3 个答案:

答案 0 :(得分:3)

使用“boost :: bind”语句执行的操作是调用derived :: test并将“b”作为“this”指针传递。重要的是要记住derived :: test的“this”指针应该是指向“派生”对象的指针 - 对你来说情况并非如此。它适用于您的特定情况,因为您没有vtable并且内存布局相同 - 但只要更改,您的程序可能会中断。

此外,它只是完全错误 - 丑陋,难以理解,容易出错的代码。你真的想做什么?

[编辑]编辑问题的新答案:你应该使用boost :: bind来创建一个功能闭包,它包装了对象&amp;单个对象中的成员函数 - 并将该对象存储在集合中。然后当你调用它时,它总是可靠的。 如果你不能在你的应用程序中使用boost ......好吧,你可以做一些像boost :: bind自己的东西(看看它是如何在boost中完成的),但你更有可能会弄错并且有错误。

答案 1 :(得分:1)

你正在做的事情是不正确的,并且在简单的例子中它将起作用,但在其他情况下可能只会引起地狱(未定义行为的可能性之一)。

由于base::testderived::test不是虚拟的,因此它们是两种不同的成员方法,所以我会简单地将它们称为base::fooderived::bar。在绑定器代码中,您强制编译器调整指向bar中定义的derived的指针,就像它在base中实际定义然后调用它一样。也就是说,您在对象上调用derived的方法或键入base!这是未定义的行为。

它不会死的原因是thisbase中的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。你想要实现的(代码)是错误的。尝试发布您需要的内容,人们将通过合理的设计提供帮助。