我在Linux下编写的一段代码中看到了一个奇怪的行为,我想分享一下,看看是否有人知道原因。 我有一个基类和一个派生类。在基类中我定义了一个虚方法,在派生类中我重新定义了该方法,具有相同的签名。然后我使用boost bind来启动一个线程。这是一个示例代码:
Class Base {
public:
virtual void DoSomething();
virtual void Init() = 0;
...
}
Class Derived : public Base {
public:
void DoSomething();
void Init();
...
}
在Derived类的Init方法中,我做了这个:
boost::thread *t = new boost::thread(boost::bind(&Base::DoSomething, this));
基类的DoSomething方法完成了它的意图,而派生类的相同方法是一个空方法,错误地留在那里。现在,在运行上面的代码时,大多数时候在线程中执行了Base类的DoSomething,因此应用程序工作正常,而有时它不起作用。经过一些调试后我注意到上面的错误,删除派生类的DoSomething解决了这个问题。在调试模式下使用Eclipse似乎总是调用派生类的DoSomething方法,而从控制台运行应用程序大部分时间都在工作,但并非总是如此。这种行为有原因吗?我的意思是,为什么有时绑定函数使用基类方法,有时候派生类的方法相同?
提前致谢
编辑以响应@pmr
很难展示一个完整的工作示例,我将尝试展示如何使用这些类。
首先我实例化一个Derived对象,然后在init函数中,我用上面显示的初始化代码启动线程。 DoSomething有一个迭代在向量上的while循环,但这不是我想的那一点。
void Derived::Init()
{
...
boost::thread *t = new boost::thread(boost::bind(&Base::DoSomething, this));
}
void Base::DoSomething()
{
while(true) {
...
}
}
void Derived::DoSomething()
{
}
正如您在此代码中看到的,Derived DoSomething方法是空的,因此有时我没有看到任何处理,而是在Base DoSomething函数中发生。
答案 0 :(得分:4)
这是一个疯狂的猜测:用于启动线程的对象实际上已被破坏!由于虚拟函数的绑定在销毁期间发生了变化(当对象被销毁时,所有虚函数都会解析,就像所使用的对象是当前正在销毁的类的类型一样)。为此,通常将“vtable指针”重置为指向合适的“虚拟功能表”。一旦基地被摧毁,就没有必要进一步摧毁这个物体。
这很适合你对随机行为的解释:有时父线程的执行速度足以达到基类构造函数,有时它没有。在使用调试模式进行编译时,父线程在销毁对象之前显然需要很长时间。你的声明在很多情况下一切正常都没有真正破坏那个图像:经常有错误的代码看起来好像它可以工作,虽然它实际上在更密切地检查时显示了erradic行为。
答案 1 :(得分:2)
我想我已经找到了这种行为的原因:起初我在Base类构造函数中调用了线程构造函数。我认为这是问题所在,因为基本构造函数在派生之前被调用,有时创建的vtable指向空的派生函数,有时线程是在创建vtable之前启动的,因此bind函数使用了基本方法,它做了它的意思。我想使用debug会引入一些延迟,所以使用调试器时线程总是绑定到派生类方法,导致错误的行为。另外,我尝试在init函数中移动线程创建,这样总是调用派生函数。
答案 2 :(得分:0)
我们在内部操作系统中遇到同样的问题,我们在构造对象时将一些虚函数绑定到另一个工作线程。如果OS在我们完成派生类的构造函数之前切换到工作线程,则worker将使用基类类型调用“this”。所以,我认为我们可以描述它:“this”指针在构造函数和反构造函数中不是线程安全的。