代码之家  ›  专栏  ›  技术社区  ›  jakogut

纯虚拟方法调用

  •  10
  • jakogut  · 技术社区  · 14 年前

    编辑:解决

    我现在正在处理一个多线程项目,在那里我有一个基本的工人类,其中有不同的工人类从中继承。在运行时,工作类成为线程,然后根据需要执行工作。

    现在,我已经编写了一个控制器,它应该维护一个指向所有工作人员的指针数组,这样它就可以从工作人员那里检索信息,并在以后修改其中的变量。

    我创建了一个指向基类指针的指针:

    baseWorkerClass** workerPtrArray;
    

    然后,在director的构造函数中,我动态地为基础工作者类分配一个指针数组:

    workerPtrArray = new baseWorkerClass*[numWorkers];
    

    在每个工作线程的构造函数中,工作线程调用Director中的函数,该函数用于将该工作线程的指针存储在数组中。

    以下是导演存储指针的方式:

    Director::manageWorker(baseWorkerClass* worker)
    {
        workerPtrArray[worker->getThreadID()] = 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;
    };
    

    在完成每个辅助变量的初始化之后,我应该得到一个指向baseworkerclass对象的指针数组。这意味着,例如,我应该能够使用某个工作进程中给定变量的ID作为数组的索引来获取该变量的值,如下所示:

    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线程是在工作线程之前创建的,这可能意味着重载的括号运算符在子类重写之前调用纯虚拟函数。我在调查这个。

    7 回复  |  直到 11 年前
        1
  •  12
  •   John Feminella    14 年前

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

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

    想必 getThreadID() 不是一个纯粹的虚拟函数,或者你会有(希望!)获取了一个关于不在中重写它的编译器错误 工件变型 . 但是 GETSHIDEADD() 可能会调用您应该重写的其他函数,但这些函数是在抽象类中调用的。你应该仔细检查 GETSHIDEADD() 以确保在正确初始化子类之前,不会做任何依赖于该子类的异常操作。

    更好的解决方案可能是将此类多阶段初始化分离为单独的方法,或者设计 Director baseWorkerClass 这样它们就不具有这种初始化时间依赖性。

        2
  •  3
  •   Igor Zevaka    14 年前

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

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

        3
  •  2
  •   AProgrammer    14 年前

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

        4
  •  2
  •   Chris Becke    14 年前

    在初始化期间,类只是部分构造的。具体地说,构造函数必须从最前面的类开始执行,这样每个派生类的构造函数就可以安全地访问其基本成员。

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

    也就是说,在构造过程中,纯虚函数实际上是纯虚函数。现代C++编译器正在更好地捕捉这一点,但在很多情况下,它可能会以非法的方式“埋葬”非法调用,这样编译器就不会注意到错误。

    故事的寓意:不要在构造函数中做任何将调用虚拟函数的事情。它不会像你期望的那样。即使不纯净。

        5
  •  1
  •   David Smith    14 年前

    我没有看到在您的任何代码示例中构造的变量类。您确定要传递的ID在工作数组的范围内吗?另外,您正在使用“new”构建对象,对吗?如果您在堆栈上构造了对象,它将向控制器注册自己,但在构造函数返回之后,对象将立即被销毁,但控制器将保留指向堆栈上对象的指针。

    此外,您的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
    };
    
        6
  •  0
  •   sirbrialliance    13 年前

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

    我通过做一个干净的构建来解决这个问题。

        7
  •  0
  •   Community SushiHangover    11 年前

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

    我建议您尝试使用内存调试工具,例如 valgrind MALLOC_CHECK_=2 . 在Unix上,很容易获得针对此类致命错误的stacktrace。只需在gdb下运行应用程序,或者 TotalView ,当错误发生时,它将自动停止,您可以查看堆栈。