这个问题似乎与线程有关并且很快就能运行程序。我有2个类,ThreadParent
和ThreadChild
,其中一个继承自另一个。 ThreadParent
创建一个线程并运行函数func
,声明为静态以规避指针问题。但我希望继承的类如ThreadChild
决定线程的确切行为,func
调用虚函数Do
。
然而,当创建ThreadChild
对象并立即运行线程时,会在开始时调用ThreadParent::Do
一次,并且所有后续调用都是ThreadChild::Do
。有趣的是,当我在调用Do
之前稍等一下时,它就不会发生。
有没有比等待更好的解决方案?更重要的是,为什么会发生这种情况?
这是一个小而完整的例子。它创建一个ThreadChild
对象,每200ms执行一次Do
。程序在1秒后结束(等待输入按下)。
#include <iostream>
#include <windows.h>
#include <thread>
// The parent class
class ThreadParent {
protected:
bool _loopThread; //setting this to false should end the thread
std::thread _thread; //the thread
public:
// Basic constructor
ThreadParent(ThreadParent* child)
: _loopThread(true),
_thread(func, child, &_loopThread) {}
// Stops the thread and waits for it to finish
void StopThread() {
_loopThread = false;
_thread.join();
}
protected:
// The function the thread will be running (static because of pointer issues)
static void func(ThreadParent* child, bool* loopThread) {
//Sleep(10); //<- uncomment to solve the problem?
while (*loopThread) {
child->Do(); // Do is called every 200ms
Sleep(200);
}
}
// The function which is called repeatedly until _loopThread is set to false
virtual void Do() {
std::cout << "Parent call\n";
}
};
// The child class
class ThreadChild : public ThreadParent {
public:
// Basic constructor
ThreadChild()
: ThreadParent(this) {}
protected:
// Redefines Do() with another message
void Do() {
std::cout << "Child call\n";
}
};
// The program
int main() {
ThreadChild thread; // Create and run the thread
Sleep(1000); // Run the thread for 1s
thread.StopThread(); // End it
std::cout << "Press <enter> to terminate...";
std::cin.get(); // Wait for user to end program
return 0;
}
输出:
Parent call
Child call
Child call
Child call
Child call
Press <enter> to terminate...
答案 0 :(得分:3)
在构造期间,基类子对象在派生类之前构造。在基类体内,动态类型实际上是基类的类型,因此动态函数调度(虚函数调用)将调用基类的相应函数。根据时间的不同,您将看到正在调用的任一函数。
为了解决这个问题,只需在构造完成后调用的第二个初始化函数中显式启动线程。
BTW:static
功能是一个红色的鲱鱼,你不会避免任何错误。此外,创建线程层次结构通常是个坏主意。相反,您的类实例表示任务或作业,这些任务或作业可能会也可能不会在单独的线程中执行。将这些对象紧密地耦合到线程可能是个坏主意。此外,将指针传递给基类构造函数的方式似乎很脆弱,因为它创建了一个首先不应该存在的依赖项。
答案 1 :(得分:0)
当实例化派生类时,首先调用基类构造函数,并将vtable指针acb
初始化为基类vtable,它保存指向vptr
的指针。只有在派生类构造函数运行时,vtable指针ThreadParent::Do
才会被覆盖(指向)指向包含vptr
的指针的类vtable。
因此,从基类构造函数调用虚方法将始终调用基类实现,而不是派生类重写方法。