通过基类引用派生类后打印的错误值

Bad value printed after referring to derived class through base class

本文关键字:打印 错误 基类 引用 派生      更新时间:2023-10-16

在实际应用程序中遇到类似情况后,我决定制作一个演示,该演示表明,如果我将派生类存储为指向基类的指针,并调用虚拟方法,派生类的行为将不正确。请参阅下面的代码:

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;
}

预期输出为:

3
5 42

相反,我收到:

42
5 42

有趣的是,如果我在 Foo 和 Bar 类中用原始 int 替换 IntWrapper 引用,则打印的值将是正确的。有人可以向我解释为什么会发生这种行为吗?

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

您正在创建一个临时IntWrapper,该临时作为对构造函数的引用传递,用于保存该引用供以后使用。由于临时在构造函数之后被破坏,因此您在foobar中的引用无效,并且您正在调用未定义的行为,这意味着任何操作都可能发生。

您需要创建将传递给构造函数的IntWrapper变量,如下所示:

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

或者,您的类应该复制传递的IntWrapper,而不是保留对它们的引用。

您所有的成员引用都悬而未决,因此您有 UB。

Base* foo = new Foo(IntWrapper{3});创建一个临时 IntWrapper(在完整表达式的末尾销毁(;在 foo 中保存对此临时的引用。当您下次使用 foo 时,保存的引用悬而未决(因为临时引用已被销毁(,并且您有未定义的行为。- 理查德·克里滕

我在 OP 的示例代码中添加了一些 printf 调试以进行演示。现在,评论中描述的效果变得非常明显(恕我直言(:

#include <iostream>
struct IntWrapper {
int value;
IntWrapper(int value): value(value)
{ 
std::cout << "IntWrapper::IntWrapper(" << value << ")n";
}
~IntWrapper()
{ 
std::cout << "IntWrapper::~IntWrapper(" << value << ")n";
}
};
struct Base {
Base(const char *text)
{
std::cout << text << "::" << text << "()n";
}
virtual ~Base() = default;
virtual void myMethod() = 0;
};
struct Foo : public Base {
const IntWrapper& x;
Foo(const IntWrapper& x): Base("Foo"), 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): Base("Bar"), 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});
std::cout << "foo->myMethod();n";
foo->myMethod();
std::cout << "bar->myMethod();n";
bar->myMethod();
return 0;
}

输出:

IntWrapper::IntWrapper(3)
Foo::Foo()
IntWrapper::~IntWrapper(3)
IntWrapper::IntWrapper(5)
IntWrapper::IntWrapper(42)
Bar::Bar()
IntWrapper::~IntWrapper(42)
IntWrapper::~IntWrapper(5)
foo->myMethod();
3
bar->myMethod();
5 42

科里鲁的现场演示

注意:

样本与预期输出匹配的事实不应被误解。它仍然是未定义的行为。