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

新手上路,请多包涵

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

基类:

 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();
}

派生类:

 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...
}

原文由 Andrew 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.1k
2 个回答

您不应该在构造或销毁期间调用虚函数,因为调用不会按照您的想法进行,如果他们这样做了,您仍然会不高兴。如果你是一个正在恢复的 Java 或 C# 程序员,请密切关注这个 Item,因为这是那些语言曲折而 C++ 曲折的地方。

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

虚拟调用的规则不同。 C++ 2003,第 12.7 节“构造和破坏”,说:

让我们刷新一些旧的记忆……

成员函数,包括虚函数 (10.3),可以在构造或销毁 (12.6.2) 期间调用。当从构造函数(包括从数据成员的 mem-initializer)或从析构函数直接或间接调用虚函数时,调用应用的对象是正在构造或销毁的对象,调用的函数是在构造函数或析构函数自己的类或其基类之一中定义的函数,但不是在派生自构造函数或析构函数类的类中覆盖它的函数,或在最派生对象的其他基类之一中覆盖它的函数(1.8 )。如果虚函数调用使用显式类成员访问(5.2.5)并且对象表达式引用正在构造或销毁的对象,但其类型既不是构造函数或析构函数自己的类也不是其基类之一,则调用未定义。

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

在构造或破坏期间切勿调用虚函数 摘自 Effective C++,第三版,Scott Meyers 2005 年 6 月 6 日

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

原文由 Saqlain 发布,翻译遵循 CC BY-SA 3.0 许可协议

就 C++ 标准而言:

  • 如果您在构造函数或析构函数中调用虚函数,则该函数被动态调度,就好像它的动态类型是当前正在执行的构造函数/析构函数的动态类型(§12.7/4)
  • 如果该函数发生在纯虚拟上,则这是 _未定义的行为_(第 10.46 节); Itanium 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();
}

诀窍?在 finalize() 被称为破坏 _interface 的点还没有开始!对析构函数的调用将在稍后发生;因此,您不会遭受半死不活的对象的命运。

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


参考文献(n3337):

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

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

原文由 Matthieu M. 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题