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?
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 IntWrapper
s, instead of holding a reference to them.