编辑:已解决
我现在正在开发一个多线程项目,我有一个基础工作者类,具有从中继承的不同工作类。在运行时,工作类成为线程,然后根据需要执行工作。
现在,我已经编写了一个Director,它应该维护一个指向所有worker的指针数组,以便它可以从中检索信息,以及稍后修改它们中的变量。
我是通过创建指向基类指针的指针来完成的:
baseWorkerClass** workerPtrArray;
然后在Director的构造函数中,我动态地为基础worker类分配一个指针数组:
workerPtrArray = new baseWorkerClass*[numWorkers];
在每个工作线程的构造函数中,worker调用director中的一个函数,该函数用于将该worker的指针存储在数组中。
以下是导演存储指针的方式:
Director::manageWorker(baseWorkerClass* worker)
{
workerPtrArray[worker->getThreadID()] = worker;
}
以下是工人变体的示例。每个worker都继承自基类worker类,而base worker类包含纯虚函数,这些函数应该存在于所有worker变量中,以及一些在所有worker之间共享的变量。
class workerVariant : protected baseWorkerClass
{
public:
workerVariant(int id)
: id(id)
{
Director::manageWorker(this);
}
~workerVariant()
{
}
int getThreadID()
{
return id;
}
int getSomeVariable()
{
return someVariable;
}
protected:
int id;
int someVariable
};
然后baseWorkerClass看起来像这样:
class baseWorkerClass
{
public:
baseWorkerClass()
{
}
~baseWorkerClass()
{
}
virtual int getThreadID() = 0;
virtual int getSomeVariable() = 0;
};
在每个worker变量完成初始化之后,我应该得到一个指向baseWorkerClass对象的指针数组。这意味着我应该能够,例如,使用其ID作为数组的索引来获取某个worker中给定变量的值,如下所示:
workerPtrArray[5]->getSomeVariable(); // Get someVariable from worker thread 5
问题是这段代码导致Windows可执行文件崩溃,没有任何解释原因,而且在Linux中,它说:
纯虚方法叫做
在没有活动异常的情况下终止被叫 中止
我可以发誓我曾经在某个时候工作过,所以我对于搞砸了什么很困惑。
存在问题的实际未修改代码:
工人变体标题:http://pastebin.com/f4bb055c8
工作人员变体源文件:http://pastebin.com/f25c9e9e3
基础工人类头:http://pastebin.com/f2effac5
基础工作者类源文件:http://pastebin.com/f3506095b
导演标题:http://pastebin.com/f6ab1767a
导演源文件:http://pastebin.com/f5f460aae
编辑:额外的信息,在manageWorker函数中,我可以从指针“worker”调用任何纯虚函数,它工作得很好。在manageWorker函数之外,当我尝试使用指针数组时,它会失败。
编辑:现在我想一想,线程的入口点是operator()。 Director线程在worker之前创建,这可能意味着重载的括号运算符在被子类覆盖之前调用纯虚函数。我正在研究这个问题。
答案 0 :(得分:12)
问题似乎是在Director::manageWorker
个实例的构造函数中调用了workerVariant
:
Director::manageWorker(baseWorkerClass* worker) {
workerPtrArray[worker->getThreadID()] = worker;
}
大概getThreadID()
不是一个纯虚函数,或者你希望(希望!)在workerVariant
中没有覆盖它时遇到编译器错误。但是getThreadID()
可能会调用您应该覆盖的其他函数,但是在抽象类中调用它们。您应该仔细检查getThreadID()
的定义,以确保在正确初始化之前,您没有做任何不利于子类的事情。
更好的解决方案可能是将这种多阶段初始化分离为单独的方法,或设计Director
和baseWorkerClass
,使它们没有这种初始化时间相互依赖性
答案 1 :(得分:3)
如果没有看到完整的代码,我会猜测你是在走出workerPtrArray
所指向的内存块的边界。它肯定会有意义,因为它抱怨调用纯虚函数。如果被解除引用的内存是垃圾,那么运行时根本就无法理解它,并且会发生奇怪的事情。
尝试将断言置于您要取消引用数组的关键位置,以确保索引有意义。即限制为4名工人,并确保id小于4。
答案 2 :(得分:2)
调用纯虚函数意味着在启动后代的构造函数的执行之前调用基类中纯的成员。在非多线程程序中,这意味着直接或间接地在基类的构造函数中。在多线程程序中,当构造函数在构造函数中启动线程并且系统在终止构造函数之前执行线程时,也会发生这种情况。
答案 3 :(得分:2)
在初始化期间,仅部分构造类。具体来说,构造函数必须从最祖先的类中执行,这样每个派生类的构造函数都可以安全地访问其基类成员。
这意味着部分构造的类的vtable不能处于其最终状态 - 如果允许虚方法调用派生类,那么在调用类构造函数之前将调用这些方法。
这意味着,在构建过程中,纯虚函数实际上是纯虚函数。现代c ++编译器在抓住这个问题方面变得越来越好 - 但在许多情况下,它可能会以一种编译器没有注意到错误的方式“掩埋”非法调用。
故事的道德:在你的构造函数中不做任何会调用虚函数的事情。它只是不会做你期望的。即使它不纯粹。
答案 4 :(得分:1)
我没有看到在您的任何代码示例中构造变体类。你确定传递的id在worker数组的范围内吗?另外,你正在使用'new'构建对象,对吧?如果您在堆栈上构造了对象,它将向Director注册自己,但是在构造函数返回后,对象将立即被销毁,但是Director将保留它指向堆栈中对象的指针。
此外,你的baseWorkerClass析构函数应该与workerVariant析构函数一起是虚拟的,以确保在删除baseWorkerClass数组时调用它们。
从我的评论到另一个问题,考虑使用std :: vector而不是双指针。它更易于维护和理解,并且无需维护阵列。
好像你在这里添加了一个不必要的抽象层。我不认为id应该真正成为子类接口的一部分。我觉得这样的事情对你来说可能会更好:
class baseWorkerClass
{
public:
baseWorkerClass(int id) :
id( id )
{
}
virtual ~baseWorkerClass()
{
}
int getThreadID(){ return id; };
virtual int getSomeVariable() = 0;
protected:
int id;
};
class workerVariant : protected baseWorkerClass
{
public:
workerVariant(int id) :
baseWorkerClass( id )
{
Director::manageWorker(this);
}
virtual ~workerVariant()
{
}
int getSomeVariable()
{
return someVariable;
}
protected:
int someVariable
};
答案 5 :(得分:0)
你是否有机会在破坏物体后访问它们?因为在销毁期间,vtable指针逐渐“回滚”,以便vtable条目将指向基类的方法,其中一些是抽象的。删除对象后,内存可以保留在基类的析构函数中。
我建议您尝试使用内存调试工具,例如valgrind或MALLOC_CHECK_=2。同样在unix上,很容易获得这种致命错误的堆栈跟踪。只需在gdb或TotalView下运行您的应用程序,并且在错误发生时它将自动停止,您可以查看堆栈。
答案 6 :(得分:0)
我收到此错误消息一次,虽然它与提问者的确切情况无关,但我希望它可能对其他人有用:
我通过干净的构建解决了这个问题。