GCC 抛出“调用纯虚拟方法”,但在优化时不会抛出

问题描述 投票:0回答:3

我有一个抽象基类 ITracer,带有纯虚方法 logMessage。 ITracer 还有一个虚拟析构函数。 我有一个派生类 NullTracer,它实现了 logMessage。

我有一个类 TestClass,其构造函数可以选择采用 const-ref ITracer。如果未提供 ITracer,则会实例化 NullTracer。

TestClass 有一个方法 test,它调用其 ITracer 的 logMessage。 在 GCC 11.2 中,会抛出“调用的纯虚拟方法”,并将“hello”打印到标准输出。 使用 GCC 11.2 和 -O2,不会引发异常,并且“hello”和“test”都会打印到 stdout。

首先,在非优化的情况下,我做错了什么?我不明白我调用了哪些纯虚函数,NullTracer显然有一个实现。

其次,在优化后的情况下,为什么不再出现异常,为什么它会按照我期望的方式执行?

编辑:不敢相信我错过了悬空的参考。谢谢

#include <iostream>

class ITracer {
   public:
    virtual ~ITracer() = default;
    virtual void logMessage() const = 0;
};

class NullTracer : public ITracer {
   public:
    void logMessage() const override { std::cout << "test" << std::endl; };
};

class TestClass {
   public:
    TestClass(const ITracer& tracer = NullTracer()) : m_tracer(tracer) {}

    void test() {
        std::cout << "hello" << std::endl;
        m_tracer.logMessage();
    }

   private:
    const ITracer& m_tracer;
};

int main() {
        TestClass test;
        test.test();
}

https://godbolt.org/z/br6WxacKo

c++
3个回答
3
投票

TestClass
构造函数创建临时
NullTracer
对象时,常量引用参数
tracer
确保该对象仅在构造函数调用的生命周期内存在。当构造函数退出时,临时对象将被销毁。

即使

m_tracer
类成员也是 const 引用,它也不会进一步延长临时
NullTracer
对象的生命周期。因此,您最终通过对无效对象的
悬挂引用
调用logMessage(),这是未定义的行为

您需要显式延长

NullTracer
对象的生命周期,方法是:

  • 按值将对象复制到另一个类成员中,然后设置

    m_tracer
    来引用该成员

  • 设置

    m_tracer
    来引用静态
    NullTracer
    对象

  • 使用指针代替引用,然后动态创建

    NullTracer
    对象


1
投票

在所有情况下你都在做同样的错误,即有一个悬空引用并假设先前的对象仍然存在。
事实并非如此,调用是未定义的行为,无论发生什么都会发生。

具体来说,您将成员

m_tracer
绑定到默认参数
NullTracer()
,这会创建一个临时对象。在 ctor 调用结束时,该临时对象被销毁。

你应该做的是在某个地方有一些具有静态生命周期的

NullTracer
,并参考它。

NullTracer defaultNullTracer;

class TestClass {
public:
    TestClass(const ITracer& tracer = defaultNullTracer) : m_tracer(tracer) {}
    TestClass(const ITracer&&) = delete; // disabled for safety

0
投票

这些答案实际上有助于解决我遇到的相同问题,因此我决定永远不要从派生对象的函数中调用已定义的函数。

class hwnd_base
{    
    virtual void vrt_Scroll_It() = 0;

     void Do_Nothing()
     {
           // This is ok; calls the derived object's vrt_Scroll_It()
           vrt_Scroll_It();
     }
};

class hwnd_derived
{    
    // function defined here in the derived class
    // ***Never call this from any hwnd_derived functions***
    void vrt_Scroll_It() { drv_Scroll_It();}

    void Do_Something()
    {
        // Not OK; compiles fine but, app crashes with 'this' = 0x########
        // the pure virtual function (which does not exist) is called 
        // vrt_Scroll_It();

        // This is OK
        drv_Scroll_It();
    }
};
© www.soinside.com 2019 - 2024. All rights reserved.