c++referencetemporary-objects

Bad value printed after referring to derived class through base class


After encountering a similar situation in a real-world application I decided to put together a demo which shows that if I store derived classes as a pointer to the base class, and call a virtual method the behavior of the derived class will be incorrect. See the code below:

struct IntWrapper { int value; };

struct Base {
    virtual ~Base() = default;
    virtual void myMethod() = 0;
};

struct Foo : public Base {

    const IntWrapper& x;

    Foo(const IntWrapper& x) : x(x) {}

    void myMethod() override {
        std::cout << std::to_string(x.value) << std::endl;
    }

};

struct Bar : public Base {

    const IntWrapper& x;
    const IntWrapper& y;

    Bar(const IntWrapper& x, const IntWrapper& y) : x(x), y(y) {}

    void myMethod() override {
        std::cout << std::to_string(x.value) << " " << std::to_string(y.value) << std::endl;
    }

};

int main()
{
    Base* foo = new Foo(IntWrapper{3});
    Base* bar = new Bar(IntWrapper{5}, IntWrapper{42});
    foo->myMethod();
    bar->myMethod();
    return 0;
}

The expected output would be:

3
5 42

Instead I receive:

42
5 42

Interestingly, if I replace IntWrapper reference with a primitive int in the classes Foo and Bar the printed values will be correct. Can somebody explain to me why this behavior happens?


Solution

  • Base* foo = new Foo(IntWrapper{3});
    Base* bar = new Bar(IntWrapper{5}, IntWrapper{42});
    

    You're creating an IntWrapper temporary that you're passing as a reference to the constructors, that save that reference for later use. As the temporary gets destructed after the constructor, your references inside foo and bar are invalid, and you're invoking undefined behavior, meaning anyhting can happen.

    You need to either create IntWrapper variables that you will pass to the constructors, like so:

    IntWrapper x{3};
    IntWrapper y{5};
    IntWrapper z{42};
    Base* foo = new Foo(x);
    Base* bar = new Bar(y,z);
    

    Or your classes should make copies of passed IntWrappers, instead of holding a reference to them.