10

编辑:已解决

我现在正在做一个多线程项目,我有一个基础工作类,其中有不同的工作类继承自它。在运行时,工作类成为线程,然后根据需要执行工作。

现在,我已经编写了一个 Director,它应该维护一个指向所有工作人员的指针数组,以便它可以从中检索信息,以及稍后修改其中的变量。

我通过创建一个指向基类指针的指针来做到这一点:

baseWorkerClass** workerPtrArray;

然后在 Director 的构造函数中,我动态分配一个指向基工作类的指针数组:

workerPtrArray = new baseWorkerClass*[numWorkers];

在每个worker线程的构造函数中,worker调用director中的一个函数,该函数旨在将该worker的指针存储在数组中。

以下是 director 存储指针的方式:

Director::manageWorker(baseWorkerClass* worker)
{
    workerPtrArray[worker->getThreadID()] = worker;
}

这是一个工人变体的例子。每个worker都继承自基础worker类,基础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 作为数组的索引来获取给定变量的值,如下所示:

workerPtrArray[5]->getSomeVariable(); // Get someVariable from worker thread 5

问题是这段代码导致 Windows 可执行文件崩溃,没有任何解释原因,在 Linux 中,它说:

纯虚方法调用
terminate 调用时没有活动异常
Aborted

我本可以发誓我在某个时候可以做到这一点,所以我对自己搞砸了什么感到困惑。


有问题的实际未修改代码:

Worker 变体头文件:http: //pastebin.com/f4bb055c8
Worker 变体源文件: http: //pastebin.com/f25c9e9e3

基础工作者类头文件:http: //pastebin.com/f2effac5
基础工作者类源文件: http: //pastebin.com/f3506095b

Director 头文件:http: //pastebin.com/f6ab1767a
Director 源文件: http: //pastebin.com/f5f460aae


编辑:额外信息,在 manageWorker 函数中,我可以从指针“worker”调用任何纯虚函数,它工作得很好。在 manageWorker 函数之外,当我尝试使用指针数组时,它会失败。

编辑:现在我考虑一下,线程的入口点是 operator()。Director线程是在worker之前创建的,这可能意味着重载的括号运算符在被子类覆盖之前正在调用纯虚函数。我正在调查这个。

4

7 回答 7

12

问题似乎是在实例Director::manageWorker的构造函数中调用的:workerVariant

Director::manageWorker(baseWorkerClass* worker) {
    workerPtrArray[worker->getThreadID()] = worker;
}

大概getThreadID()不是一个纯虚函数,或者你会(希望!)得到一个编译器错误,关于没有在workerVariant. 但是getThreadID()可能会调用您应该覆盖但正在抽象类中调用的其他函数。您应该仔细检查 的定义,getThreadID()以确保在正确初始化子类之前,您没有做任何取决于子类的不良行为。

更好的解决方案可能是将这种多阶段初始化分离到一个单独的方法中,或者设计为DirectorbaseWorkerClass具有这种初始化时间的相互依赖性。

于 2010-01-30T09:03:28.227 回答
3

如果没有看到完整的代码,我会冒险猜测您正在走出workerPtrArray. 这肯定是有道理的,因为它抱怨调用纯虚函数。如果被取消引用的内存是垃圾,那么运行时根本无法理解它,并且会发生奇怪的事情。

尝试将断言放在要取消引用数组的关键位置,以确保索引有意义。即限制为 4 个工人,并确保 id 小于 4。

于 2010-01-30T11:12:51.180 回答
2

纯虚函数调用是指在子类的构造函数开始执行之前调用基类中的纯成员。在非多线程程序中,这意味着直接或间接在基类的构造函数中。在多线程程序中,当构造函数在构造函数中启动线程并且系统在终止构造函数之前执行线程时,也会发生这种情况。

于 2010-01-30T10:54:53.727 回答
2

在初始化期间,类仅部分构造。具体来说,构造函数必须从最祖先的类开始执行,以便每个派生类的构造函数都可以安全地访问其基成员。

这意味着部分构造的类的 vtable 不能处于其最终状态 - 如果允许虚拟方法调用派生类,则将在调用该类构造函数之前调用这些方法。

这意味着,在构造过程中,纯虚函数实际上是纯虚函数。现代 c++ 编译器在捕捉这一点方面做得越来越好 - 但在许多情况下,它可能以编译器不会注意到错误的方式“隐藏”非法调用。

故事的寓意:不要在构造函数中做任何会调用虚函数的事情。它只是不会做你所期望的。即使它不纯净。

于 2010-01-30T11:49:46.970 回答
1

我没有在您的任何代码示例中看到正在构造的变体类。您确定传递的 id 在工作人员数组的范围内吗?另外,您正在使用“新”构造对象,对吗?如果你在堆栈上构造了对象,它会向 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
};
于 2010-01-30T21:23:56.000 回答
0

在它们被破坏后,您是否有机会访问这些对象?因为在销毁过程中,vtable 指针逐渐“回滚”,因此 vtable 条目将指向基类的方法,其中一些是抽象的。删除对象后,内存可以保留在基类的析构函数期间。

我建议您尝试使用内存调试工具,例如valgrindMALLOC_CHECK_=2。同样在 unix 上,很容易获得此类致命错误的堆栈跟踪。只需在 gdb 或TotalView下运行您的应用程序,当错误发生时它会自动停止,您可以查看堆栈。

于 2010-01-30T22:10:53.703 回答
0

我曾经收到此错误消息,虽然它与提问者的确切情况无关,但我添加了这个,希望它可能对其他人有用:

我通过干净的构建解决了这个问题。

于 2011-07-15T03:46:36.690 回答