Home > database >  GCC throws "pure virtual method called", but not when optimizations are on
GCC throws "pure virtual method called", but not when optimizations are on

Time:02-28

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 instead

  • set m_tracer to refer to a static NullTracer object

  • use pointers instead of references, and then create the NullTracer object dynamically

  •  Tags:  
  • c
  • Related