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

时间:2016-02-01 12:34:13

标签: c++

当函数按值返回对象时,它将调用复制构造函数来创建临时(除非应用了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)。为什么编译器制造商都决定暂时销毁它,即使暂时不使用它?为什么他们不能避免这种情况?

4 个答案:

答案 0 :(得分:4)

当RVO将其删除时,编译器可以删除副本的副作用(这本身就很有争议),但是对象的整个存在不能简单地通过优化编译器从程序中删除,如构建和/或销毁它会产生副作用。

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

#include <iostream>

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

int main()
{
   A a;
}

答案 1 :(得分:3)

  

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

因为它应该如何运作!

真的,对于你想要优化它的情况,C ++中有参考机制;在你的情况下,不能被优化掉,因为“我检查”意味着你依靠构造函数的副作用来向你显示它被调用;如何编译器知道你在功能上不需要那种副作用?

答案 2 :(得分:1)

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

#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返回值是默认的构造对象,因此调用默认的construcotr。

答案 3 :(得分:1)

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

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

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

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

但是,您可以手动执行此优化。简单地分离构造和返回对象的函数部分,以及具有副作用的函数部分:

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)