如何修复称为运行时错误的纯虚函数?

And*_*rew 3 c++ pure-virtual

我理解为什么我得到了我得到的错误(称为纯虚函数).我试图从我的基类的析构函数中调用纯虚函数,如下所示.但是,我不知道如何重写我的代码以防止这种情况发生.以下是基类和派生类(无论如何相关部分):

基类:

TailFileManager::TailFileManager(const std::string &filename, const int fileOpenPeriod_ms)
: m_Stop(false)
{
    m_WorkerThread.reset(new boost::thread(boost::bind(&TailFileManager::TailFile, this, filename, fileOpenPeriod_ms)));
}

TailFileManager::~TailFileManager()
{
    m_Stop = true;
    m_WorkerThread->join();
}

void TailFileManager::TailFile(const std::string &filename, const int fileOpenPeriod_ms)
{
    std::ifstream ifs(filename.c_str());

    while (! ifs.is_open())
    {
        boost::this_thread::sleep(boost::posix_time::milliseconds(fileOpenPeriod_ms));
    ifs.open(filename.c_str());
    }

    ifs.seekg(0, std::ios::end);

    while (! m_Stop)
    {
        ifs.clear();

        std::string line;

        while (std::getline(ifs, line))
        {
            OnLineAdded(line);
        }

        OnEndOfFile();
    }

    ifs.close();
}
Run Code Online (Sandbox Code Playgroud)

派生类:

ETSLogTailFileManager::ETSLogTailFileManager(const std::string &filename, const int heartbeatPeriod_ms)
: TailFileManager(filename, heartbeatPeriod_ms),
  m_HeartbeatPeriod_ms(heartbeatPeriod_ms),
  m_FoundInboundMessage(false),
  m_TimeOfLastActivity(0)
{
}

ETSLogTailFileManager::~ETSLogTailFileManager()
{
}

void ETSLogTailFileManager::OnLineAdded(const std::string &line)
{
    // do stuff...
}

void ETSLogTailFileManager::OnEndOfFile()
{
    // do stuff...
}
Run Code Online (Sandbox Code Playgroud)

Saq*_*ain 10

你不应该在构造或破坏期间调用虚函数,因为调用不会按照你的想法进行,如果有的话,你仍然会不高兴.如果你是一个正在恢复的Java或C#程序员,请密切关注这个项目,因为这是这些语言的地方,而C++ zags.

重新设计你的设计,即你可以在对象被破坏之前调用一些清理函数,想法就是在const/dest期间避免虚函数(如果有的话!),如果你正在使用C++ ...

虚拟调用的规则是不同的.C++ 2003,第12.7节"构建和销毁",说:

让我们回忆起一些旧的回忆......

成员函数,包括虚函数(10.3),可以在构造或销毁期间调用(12.6.2).当直接或间接地从构造函数(包括来自数据成员的mem-initializer)或析构函数调用虚函数时,调用所适用的对象是正在构造或销毁的对象,调用的函数是一个在构造函数或析构函数自己的类或其基础中定义的,但不是在从构造函数或析构函数类派生的类中重写它的函数,或者在最派生对象的其他一个基类中重写它(1.8) ).如果虚函数调用使用显式类成员访问(5.2.5)并且object-expression引用正在构造或销毁的对象但它的类型既不是构造函数也不是析构函数自己的类或其基础之一,则结果为电话未定义.

由于这种行为差异,建议您在构造或销毁对象的虚函数时不要调用它.

在施工或销毁期间从不调用虚拟功能摘自 2005年6月6日Scott Meyers撰写的Effective C++,第三版

http://www.artima.com/cppsource/nevercall.html

  • 一个对C++有所了解的人肯定会同意我的意见:-)我所说的也是Scott Meyers的Effective C++,Third Edition中提到的,更好的是花一些时间阅读书籍然后浪费时间评论stackoverflow :-) (3认同)
  • 在链接到材料中,Scott写道:"这似乎是解决问题的合理方法",关于在析构函数中调用纯虚函数.它不是,它似乎不合理,除了完全无能.即斯科特在这里完全错了; 他所写的,事实并非如此.Scott Meyers为C++做出了很多贡献,包括Meyers的单例和删除自动生成移动构造函数和赋值运算符的原始注释.但他对某些事情也存在根本性的错误,他就是这样.所以,忘记那件作为权威.这是不正确的. (2认同)

Mat*_* M. 7

就C++标准而言:

  • 如果在构造函数或析构函数中调用虚函数,则动态调度该函数,就好像它的动态类型是正在执行的当前构造函数/析构函数的类型一样(§12.7/ 4)
  • 如果该函数发生在纯虚拟中,那么这是未定义的行为(§10.4/ 6); 在安腾ABI定义的行为:__cxa_pure_virtual被调用.

所以,你有一个棘手的问题......


该问题的一个可能的解决方案是将其分解为两部分,以便分解两部分.这可以通过以下Strategy模式实现:

  • 提供可定制的界面,您的策略
  • 提供一个封装功能的管理器类,并遵循可定制部件的策略

让我们更清楚:

class Interface {
public:
    friend class Manager;

private:
    virtual void finalize() = 0;
}; // class Interface


class Manager {
public:
    explicit Manager(std::unique_ptr<Interface>&&);

    ~Manager();

private:
    std::unique_ptr<Interface> _interface;
}; // class Manager

Manager::~Manager() {
    _interface->finalize();
}
Run Code Online (Sandbox Code Playgroud)

诀窍?在finalize()被称为破坏的地方_interface还没有开始!对析构函数的调用将在稍后发生; 因此你不会遭受半死的物体的命运.

我现在将通过一个关于join-detructor中的线程的警告来结束这个答案.请注意,在堆栈展开的情况下会自动调用析构函数,因此在失败时无限期等待可能会很危险; 特别是如果线程正在等待当前正被解除的数据应该提供的数据......这是一个经典的死锁案例.


参考文献(n3337):

§12.7/ 4成员函数,包括虚函数(10.3),可以在构造或销毁期间调用(12.6.2).当从构造函数或析构函数直接或间接调用虚函数时,包括在构造或销毁类的非静态数据成员期间,以及调用所适用的对象是正在构造的对象(称为x)或者破坏,被调用的函数是构造函数或析构函数类中的最终覆盖,而不是在更多派生类中覆盖它.

§10.4/ 6可以从抽象类的构造函数(或析构函数)调用成员函数; 对于从这样的构造函数(或析构函数)创建(或销毁)的对象,直接或间接地对纯虚函数进行虚拟调用(10.3)的效​​果是未定义的.


Che*_*Alf 5

你写,

"我试图从下面显示的基类的析构函数中调用纯虚函数."

而且有问题的代码是

TailFileManager::~TailFileManager()
{
    m_Stop = true;
    m_WorkerThread->join();
}
Run Code Online (Sandbox Code Playgroud)

令人高兴的是,在单线程执行中,这不可能称为纯虚函数.但是你正在使用的线程join可能会通过非虚拟成员函数在此对象上调用纯虚函数.如果是这样,则问题在于线程,特别是该对象的生命周期管理.

很遗憾,您没有显示相关代码.尝试将事情简化为一个小而完整的工作示例.在哪里"工作",它再现了问题.