我有一个抽象基类 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();
}
当
TestClass
构造函数创建临时 NullTracer
对象时,常量引用参数 tracer
确保该对象仅在构造函数调用的生命周期内存在。当构造函数退出时,临时对象将被销毁。
即使
m_tracer
类成员也是 const 引用,它也不会进一步延长临时 NullTracer
对象的生命周期。因此,您最终通过对无效对象的悬挂引用调用
logMessage()
,这是未定义的行为。
您需要显式延长
NullTracer
对象的生命周期,方法是:
按值将对象复制到另一个类成员中,然后设置
m_tracer
来引用该成员
设置
m_tracer
来引用静态 NullTracer
对象
使用指针代替引用,然后动态创建
NullTracer
对象
在所有情况下你都在做同样的错误,即有一个悬空引用并假设先前的对象仍然存在。
事实并非如此,调用是未定义的行为,无论发生什么都会发生。
具体来说,您将成员
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
这些答案实际上有助于解决我遇到的相同问题,因此我决定永远不要从派生对象的函数中调用已定义的函数。
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();
}
};