我应该将 ref 还是副本分配给值返回函数?

Should I assign a ref or a copy to a value returning function?

我们有一堆值返回函数:

Foo function_1(){
    Foo f;
    // ...
    return f;
}

Bar function_2(){
    Bar b;
    // ...
    return b;
}

Baz function_3(){
    Baz b;
    // ...
    return b;
}

我正在使用它们来创建局部变量的实例化:

void example(){  
    //...
    const auto foo = function_1();
    //...
    const auto bar = function_2();
    //...
    const auto baz = function_3();
}

但是,我的队友一直要求我将我所有的实例化转换为使用 &

void example(){  
    //...
    const auto& foo = function_1();
    //...
    const auto& bar = function_2();
    //...
    const auto& baz = function_3();
}

这样做的理由似乎符合以下问题:

我知道 auto& x =auto x = 要求的是两种不同的东西,行为可能会因功能的实现而有所不同。

话虽如此,我正在寻找有关选择对值返回函数执行此操作时的差异(如果有的话)的说明?行为是否保证相同?

如果它是一个值返回函数,我如何在 const auto&const auto 之间做出选择? (大概 const 在这里无关紧要?)。我在 C++ Core Guidelines 中找不到关于此的任何建议。我希望这不是一个自以为是的问题。

这是我的建议:

如果您需要对函数返回的对象进行只读访问,请使用

const auto& foo = function_1();

如果您希望能够修改返回的对象,或者不想冒险该对象可能在您不知情的情况下被其他函数删除,请制作一个副本。

auto foo = function_1();

回复评论的补充信息

当下面的程序用g++ -std=c++11 -Wall

编译时
#include <iostream>

struct Foo
{
   Foo() {}
   Foo(Foo const& copy) { std::cout << "In copy constructor\n"; }
};

Foo function_1(){
    Foo f;
    return f;
}

int main()
{
   std::cout << "-------------------------\n";
   auto f1 = function_1();
   std::cout << "-------------------------\n";
   const auto& ref = function_1();
}

我得到以下输出(构造函数没有输出)。

-------------------------
-------------------------

当用g++ -std=c++11 -Wall -fno-elide-constructors

编译同一个程序时

我得到以下输出。

-------------------------
In copy constructor
In copy constructor
-------------------------
In copy constructor

如果编译器默认不支持复制省略,使用

时会进行额外的复制
auto f1 = function_1();

恕我直言 - 当您希望 return 引用现有对象时,您应该 return 通过引用。当您希望调用者获得与其他对象无关的副本时,您应该按值 return。

例如;如果您有一个函数 return 从容器中获取一个对象并且您希望调用者能够更新原始值,那么 return 一个引用。如果调用者应该只获取他们自己的副本来使用它不应该影响容器中的原始对象,那么按值 return。

我的经验法则是值语义是最容易使用的 - 无论是 returning 对象还是将它们作为参数,所以我更喜欢 taking/returning 按值,除非我有一个明确的理由不。

您的同事试图完成编译器的工作而不是信任它,结果可能会悲观。 NRVO 得到很好的支持,如果函数是用值语义编写的,NRVO 可以省略多个副本。绑定到引用将阻止这种情况,因为引用变量将不满足此优化的条件。 A simple test to demonstrate:

#include <iostream>

struct Test {
    Test() { std::cout << "Created\n"; }
    Test(const Test&) { std::cout << "Copied\n"; }
};

Test foo() {
    Test t;
    return t;
}

Test bar_good() {
    const auto t = foo();
    return t;
}

Test bar_bad() {
    const auto& t = foo();
    return t;
}

int main() {
    const auto good = bar_good(); (void)good;

    std::cout << "===========\n";

    const auto& bad = bar_bad();  (void)bad;
}

给出输出:

Created
===========
Created
Copied

使用值语义时总共一个对象,但使用引用时是一个冗余副本。根据副本(甚至移动)的扩展程度,您可能会看到明显的性能差异。

引用的好处是它总是有用的。 如果按值捕获,复制省略可能使其就像按引用捕获一样;但是如果复制构造函数被删除,按值捕获会触发一个移动,然后销毁临时对象,这会降低效率;如果移动构造函数也被删除,编译将失败。 现在,必须在 mutable 和 const 之间做出选择:如果是可变的,那么你将需要可变的右值引用;否则,const 左值引用。