C++ 中按值传递造成的损坏

You*_*ouk 2 c++ destructor parameter-passing

在 Herbert Schildt 的《C++ IT-Tutorial》一书中,第 9 章第 368 页,指出了以下问题:

即使您按值将对象传递给函数(在这种情况下,理论上应该隔离和保护传递的对象),该对象仍然可能会被更改甚至被副作用破坏。当在函数内调用对象的析构函数以删除对象的本地副本时,可能会发生这种情况。

该段落是为了鼓励使用复制构造函数而编写的。

不幸的是,尽管书中的大多数其他问题都用简单的示例进行了记录,但我找不到重现此行为的最小示例。你能推荐一个吗?

Som*_*ude 9

发生这种情况通常是因为程序员没有遵循三、五或零的规则

当您按值传递对象时,编译器将创建代码来进行复制。以具有指针成员变量的对象为例。复制对象将复制指针,而不是它指向的数据。当副本被破坏时,它的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);
}
Run Code Online (Sandbox Code Playgroud)

调用该函数时,会创建f该对象的副本。original该副本将具有 的运行时的生命周期f。当生命周期结束时,copy它的析构函数将被调用。但由于 和originalcopy指向同一个S对象,因此 in 的指针original将变得无效。


这个问题可以通过多种方式解决。第一个也是最简单的方法是创建一个Bad复制构造函数,它创建自己的 副本S,因此它独立于原始副本。

一个更好的解决方案是使用类似的东西std::shared_ptr<S>。这通常用于标记所有权,但也可以在这里工作。

更好的解决方案是S根本不将对象作为指针,因为编译器生成的复制构造函数将确保S也创建该对象的副本。尽管这可能并非在所有情况下都是可能的(想想多态性)。

但我认为最好的解决方案是通过引用传递对象:

void f(Bad const& reference) { ... }
Run Code Online (Sandbox Code Playgroud)