I have an abstract base class, ITracer, with pure virtual method logMessage. ITracer also has a virtual destructor. I have a derived class, NullTracer, which implements logMessage.
I have a class, TestClass, whose constructor optionally takes a const-ref ITracer. If no ITracer is provided, a NullTracer is instantiated.
TestClass has a method, test, which calls its ITracer's logMessage. With GCC 11.2, "pure virtual method called" is thrown and "hello" is printed to stdout. With GCC 11.2 and -O2, no exceptions are thrown and both "hello" and "test" are printed to stdout.
First, in the non-optimized case, what am I doing wrong? I don't understand which pure virtual functions I calling, NullTracer clearly has an implementation.
Second, in the optimized case, why is there no longer an exception and why does it execute the way I am expecting it to?
#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
CodePudding user response:
You are doing the same thing wrong in all cases, namely having a dangling reference and assuming the prior object still is there.
It isn't, the call is undefined behavior, and whatever happens will happen.
Specifically, you are binding the member m_tracer
to the default-argument NullTracer()
which creates a temporary object. At the end of the ctor-call, that temporary is destroyed.
What you should do is have some NullTracer
with static lifetime somewhere, and refer to that.
NullTracer defaultNullTracer;
class TestClass {
public:
TestClass(const ITracer& tracer = defaultNullTracer) : m_tracer(tracer) {}
CodePudding user response:
When the TestClass
constructor creates a temporary NullTracer
object, the const-reference parameter tracer
ensures that the object lives only for the lifetime of the constructor call. When the constructor exits, the temporary object gets destroyed.
Even though the m_tracer
class member is also a const-reference, it DOES NOT extend the lifetime of the temporary NullTracer
object any further. And so, you end up calling logMessage()
via a dangling reference to an invalid object, which is undefined behavior.
You need to explicitly extend the lifetime of the NullTracer
object, either by:
copying the object by value into another class member, and then set
m_tracer
to refer to that member insteadset
m_tracer
to refer to a staticNullTracer
objectuse pointers instead of references, and then create the
NullTracer
object dynamically