2012-01-21 24 views
4

我在Linux下编写的一段代码中看到了一个奇怪的行为,我想分享一下,看看是否有人知道这个原因。 我有一个基类和一个派生类。在基类中,我定义了一个虚拟方法,并在派生类中重新定义了该方法,并使用相同的签名。然后我使用boost绑定来启动一个线程。下面是一个示例代码:虚拟函数和提升绑定奇怪行为

Class Base { 
public: 
    virtual void DoSomething(); 
    virtual void Init() = 0; 
    ... 
} 

Class Derived : public Base { 
public: 
    void DoSomething(); 
    void Init(); 
    ... 
} 

在派生类中我这样做的init方法:

boost::thread *t = new boost::thread(boost::bind(&Base::DoSomething, this)); 

基类的DoSomething的方法做了什么,它的意思做的,而同样的方法的派生类是一个空的方法,留在那里是错误的。现在,在运行上面的代码时,大部分时间都是在线程中执行了基类的DoSomething,所以应用程序运行正常,但有时无法正常工作。经过一些调试后,我注意到上面的错误,并删除派生类的DoSomething解决了这个问题。在调试模式下使用Eclipse似乎始终调用派生类的DoSomething方法,而在大多数情况下从控制台运行应用程序却并非总是如此。是否有这种行为的原因?我的意思是,为什么有时候绑定函数会使用基类方法,有时也会使用派生类的相同方法?

在此先感谢

编辑回应@pmr

这将是很难表现出一个完整的工作示例,我会尽量表现出位的类是如何使用的。

首先我实例化一个Derived对象,然后在init函数中我用上面显示的初始化代码启动线程。 DoSomething有一个while循环,它在vector上迭代,但这不是我想的。

void Derived::Init() 
{ 
    ... 
    boost::thread *t = new boost::thread(boost::bind(&Base::DoSomething, this)); 
} 

void Base::DoSomething() 
{ 
    while(true) { 
     ... 
    } 
} 

void Derived::DoSomething() 
{ 
} 

正如你在代码中派生DoSomething的方法是看空的,所以有时候我没有看到任何处理,这反而发生在基地的DoSomething函数。

+0

小心提供一个最小的可编译示例?或者至少说明课堂是如何使用的。 – pmr

回答

2

我想我已经找到了这种行为的原因:首先,我调用基类构造函数内的线程构造函数。我认为这是问题所在,因为基础构造函数是在派生函数之前调用的,有时创建的vtable指向空的派生函数,有时线程是在创建vtable之前启动的,因此bind函数使用基本方法,这是做它的意思。我想使用调试一些延迟引入,所以使用调试器线程总是绑定到派生类方法,导致错误的行为。另外,我尝试在init函数内部移动线程创建,并以这种方式始终调用派生函数。

4

这是一个疯狂的猜测:你用来启动线程的对象实际上已经被销毁了!由于虚拟函数的绑定在销毁过程中发生了变化(当对象被销毁时,所有虚拟函数的解析就好像所使用的对象是当前被销毁的类的类型一样)。为此,“vtable指针”通常被重置为指向适当的“虚拟功能表”。一旦基地被摧毁,就没有必要进一步摧毁物体。

这很适合你对随机行为的解释:有时父线程执行速度足够快,已经到达基类构造函数了,有时它不会。当用调试模式编译时,父线程在破坏对象之前显然一直持续很久。你的陈述表明,在许多情况下一切正常,并不能真正破坏这个形象:通常看起来有问题的代码看起来好像有效,尽管它在被仔细检查时实际上显示出了根治行为。

+0

嗨,我不认为是这种情况,因为类不会被破坏,所以在线程初始化之后我使用了同一类的其他函数(这是某种守护进程,所以应用程序运行很长时间) 。 – cpl

0

我们在内部OS面临同样的问题,我们结合一些虚函数到另一个工作线程,当对象构造的。如果操作系统在完成派生类的构造函数之前切换到工作线程,工作人员将使用基类类型调用“this”。所以,我认为我们可以描述它:“this”指针在构造函数和De构造函数中不是线程安全的。