Herbert Schildt 的《C++ IT-Tutorial》一书第 9 章第 368 页指出了以下问题:
“即使您按值将对象传递给函数,在这种情况下,理论上应该隔离和保护传递的对象,但该对象仍然可以被更改甚至被副作用破坏。当对象的析构函数被调用时,可能会发生这种情况。在函数内调用以删除对象的本地副本。”
编写该段落是为了鼓励使用复制构造函数。
不幸的是,尽管书中的大多数其他问题都用简单的示例进行了记录,但我找不到重现此行为的最小示例。你能推荐一个吗?
这通常是因为程序员没有遵循三、五或零的规则而发生。
当您按值传递对象时,编译器将创建代码来进行“浅”副本。以具有指针成员变量的对象为例。复制对象将复制 pointer 而不是它指向的数据。当副本被破坏时,它delete
内存,这意味着原始对象中的指针不再有效,因为它仍然指向现在已删除的内存。
struct S
{
// Some data
};
struct Bad
{
S* pointer;
Bad()
: pointer{ new S } // Create an object, and initialize the pointer
{}
~Bad()
{
delete pointer;
}
};
void f(Bad copy)
{
// Do something...
}
int main()
{
Bad original;
f(original);
}
调用函数
f
时,会创建对象
original
的副本。该副本的生命周期为 f
的运行时。当 copy
的生命周期结束时,它的析构函数将被调用。但由于 original
和 copy
都指向同一个 S
对象,因此 original
中的指针将变得无效。
这个问题可以通过多种方式解决。第一个也是最天真的是创建一个 Bad
复制构造函数,它创建自己的
S
副本,因此它独立于原始版本。更好的解决方案是使用 std::shared_ptr<S>
之类的东西代替。这通常用于标记所有权,但也可以在这里工作。
更好的解决方案是根本不将S
对象作为指针,因为编译器生成的复制构造函数将确保创建
S
对象的副本。但我认为最好的解决方案是通过引用传递对象:
void f(Bad const& reference) { ... }
struct bar { };
void foo(bar x, bar* y) {
delete y;
}
int main() {
bar* a = new bar;
bar& b = a;
foo(b, a);
}
foo
按值传递
b
,它不能直接通过该副本修改对象。虽然同一个函数可以通过另一个参数删除同一个对象。但我不知道这是如何或为何相关的。我无法理解最后一句话
“当在函数内调用对象的析构函数来删除对象的本地副本时,可能会发生这种情况”在上面的示例中,这意味着
x
超出范围会以某种方式影响原始
bar
对象。虽然 x
只是一个副本,但我无法想象这怎么会发生,除非 bar
一开始就被破坏了。