为啥在按值返回时总是调用复制构造函数

Posted

技术标签:

【中文标题】为啥在按值返回时总是调用复制构造函数【英文标题】:why copy constructor is always called when return by value为什么在按值返回时总是调用复制构造函数 【发布时间】:2016-02-01 12:34:12 【问题描述】:

当一个函数按值返回一个对象时,它会调用复制构造函数来创建一个临时对象(除非应用了 RVO)。该临时文件将在使用后被销毁,例如

MyClass function_return_by_value(MyClass par)

    return par;

MyClass b;
MyClass a = function_return_by_value(b);  // (1)

但是,如果根本不使用它,为什么我们需要创建这样一个临时文件呢?例如,为什么下面的代码没有被编译器“优化”以跳过临时创建和销毁?

MyClass b;
function_return_by_value(b);  // (2)

在(1)中,返回值被分配给另一个变量,RVO 很可能适用。但是在(2)中,没有任何东西可以接收返回值,为什么没有进行优化呢?我已经尝试过 gcc 4.8.4 和 vc++ 2015,MyClass 的复制构造函数在两个编译器中都为 (2) 调用了两次。为什么编译器制造商都决定制作一个临时文件然后销毁它,即使临时文件根本没有使用?他们为什么不能避免这种情况?

【问题讨论】:

取决于您的复制构造函数中调用的副作用。 您需要将par 带到const MyClass& 以防止第二次复制。 谢谢@PSkocik,但我认为你应该先阅读我的问题。 @πάνταῥεῖ 可能的副作用是什么?谢谢 作为编译器,我猜你不会事先知道要调用的函数的所有上下文。因此,在翻译时,如果缺少更多信息,请假设最坏的情况。因此,即使对于 (2)。 (但不知道标准会说/允许什么) 【参考方案1】:

当 RVO 删除副本时,编译器可以删除副本的副作用(这本身就颇具争议),但是优化编译器不能简单地从程序中删除对象的整个存在,因为只要构建和/或破坏它有副作用。

这将允许以下程序不输出任何内容,这显然是错误的:

#include <iostream>

struct A

   A()  std::cout << "Booyah\n"; 
;

int main()

   A a;

【讨论】:

【参考方案2】:

我已经尝试过 gcc 4.8.4 和 vc++ 2015,MyClass 的复制构造函数在两个编译器中都为 (2) 调用了两次。

因为它应该是这样工作的!

真的,对于您希望对其进行优化的情况,C++ 中有引用机制;在你的情况下,它不能被优化掉,因为“我检查过”意味着你依赖于构造函数的副作用来向你展示它被调用了;编译器应该如何知道您在功能上不需要这种副作用?

【讨论】:

请注意,由于复制省略(包括 RVO/NRVO),无论如何都不能依赖构造函数和析构函数的副作用。在复制省略中描述的情况下,允许编译器不调用构造函数或析构函数,即使它们具有可观察到的副作用。【参考方案3】:

您抱怨的副本不是从函数返回值到分配给它的变量的副本。它是创建返回值,因为您命令它是函数参数的副本。考虑下面的代码:

#include <iostream>

class Krowa

public:
        Krowa() std::cout << "Default krowa\n";
        Krowa(Krowa const &) std::cout << "Copied Krowa\n";
;

Krowa fun1(Krowa const & krowa)

        return krowa;


Krowa fun2()

        return Krowa;


int main()

        fun1(Krowa);
        fun2();
        return 0;

输出是

默认krowa 复制的 Krowa 默认krowa

fun1返回值是参数的副本,所以调用了复制构造函数。 fun2返回值是一个默认构造对象,所以调用了默认constructr。

【讨论】:

【参考方案4】:

构造对象的函数和不构造对象的函数是两个不同的函数。当编译器编译一个函数时,它不知道该函数将如何被调用。它怎么知道它应该编译哪个版本?一个会构建还是一个不会?

现在,让我们假设编译器确实知道在同一个翻译单元中,从不使用返回值。可以想象,编译器然后可以优化函数以从不构造对象。但是,如果稍后在另一个编译单元中调用该函数并使用结果 怎么办?该函数的优化版本将不起作用,因为它不返回任何内容!

好的,如果编译器总是编译所有返回值的函数的两个版本,并且在使用返回值时选择一个,而在不使用返回值时选择另一个。好吧,编译器仍然不允许这样做,因为对象的构造函数和析构函数可能有副作用,并且这些副作用不能简单地消失(复制省略允许这样做,但这是一种特殊情况,不适用于此处)。

好的,假设编译函数时对象的构造函数和析构函数是可见的。然后,如果编译器可以证明没有副作用,那么理论上它可以应用上面讨论的优化。鉴于这种优化发生的严格要求(可见 c'tor 和 d'tor 没有副作用)和不利影响(所有返回值函数的两个版本会增加编译时间并可能增加二进制大小),我对此表示怀疑已经认真考虑过优化类型。

不过,您可以手动进行此优化。简单的把构造和返回对象的函数部分和有副作用的函数部分分开:

void function_that_has_side_effects(const MyClass& par):

MyClass function_return_by_value(MyClass par)

    function_that_has_side_effects(par);
    return par;

现在,如果您想要副作用,但不需要返回值,只需调用 function_that_has_side_effects

MyClass b;
function_that_has_side_effects(b);  // (2)

【讨论】:

以上是关于为啥在按值返回时总是调用复制构造函数的主要内容,如果未能解决你的问题,请参考以下文章

当我们将对象作为参数传递给方法时,为啥会调用复制构造函数?

没有调用复制构造函数?

从函数返回对象时调用C ++中的复制构造函数?

c++,类的对象作为形参时一定会调用复制构造函数吗?

在哪些情况下调用 C++ 复制构造函数?

为啥隐式复制构造函数调用基类复制构造函数而定义的复制构造函数不调用?