4

我正在阅读有关 NRVO 的信息,并试图了解什么时候应该依赖它,什么时候不应该依赖它。现在我有一个问题:为什么要完全依赖 NRVO?总是可以通过引用显式传递返回参数,那么有什么理由改用 NRVO 吗?

4

4 回答 4

7

处理返回值比处理通过写入引用参数返回的方法更容易。考虑以下2种方法

C GetByRet() { ... }
void GetByParam(C& returnValue) { ... }

第一个问题是无法链接方法调用

Method(GetByRet());  
// vs. 
C temp;
GetByParam(temp);
Method(temp);

它还使功能auto无法使用。对于像这样的类型来说不是什么大问题,C但对于像这样的类型来说更重要std::map<std::string, std::list<std::string>*>

auto ret = GetByRet();
// vs.
auto value; // Error! 
GetByParam(value);

同样正如 GMacNickG 指出的那样,如果该类型C有一个普通代码无法使用的私有构造函数怎么办?也许构造函数是private或者只是没有默认构造函数。再次GetByRet像冠军一样工作并且GetByParam失败了

C ret = GetByRet();  // Score! 
// vs.
C temp; // Error! Can't access the constructor 
GetByParam(temp);
于 2013-08-14T16:51:11.227 回答
4

这不是一个答案,但在某种意义上它也是一个答案……

给定一个通过指针获取参数的函数,有一个简单的转换将产生一个按值返回的函数,并且编译器可以轻松优化。

void f(T *ptr) {     
   // uses ptr->...
}
  1. 在函数中添加对对象的引用,并将 ptr 的所有使用替换为引用

    void f(T *ptr) { T & obj = *ptr; /* uses obj. instead of ptr-> */ }

  2. 现在删除参数,添加返回类型,替换T& objT obj并将所有返回更改为 yield 'obj'

    T f() { T obj; // No longer a ref! /* code does not change */ return obj; }

  3. 此时,您有一个按值返回的函数,NRVO 是微不足道的,因为所有返回语句都引用同一个对象。

这个转换后的函数与指针传递有一些相同的缺点,但它从来没有比它更糟。但它表明,只要按指针传递是一个选项,按值返回也是一个具有相同成本的选项。

费用完全一样吗?

这超出了语言的范围,但是当编译器生成代码时,它会遵循 ABI(应用程序二进制接口),该 ABI(应用程序二进制接口)允许由不同运行的编译器(甚至同一平台中的不同编译器)构建的代码进行交互。所有当前使用的 ABI 都具有按值函数返回的共同特征:对于大型(不适合寄存器)返回类型,返回对象的内存由调用者分配,并且函数需要一个带有该内存位置的额外指针. 那是编译器看到的时候

T f();

调用约定将其转换为:

void mangled_name_for_f( T* __result )

因此,如果您比较备选方案:T t; f(&t);并且T t = f();在这两种情况下,生成的代码都会在调用者的框架中分配空间,[1],调用传递指针的函数。在函数结束时,编译器将 [2] 返回。其中 [#] 是在每个备选方案中实际调用对象构造函数的位置。两种选择的成本是相同的,不同之处在于,在 [1] 中,对象必须是默认构造的,而在 [2] 中,您可能已经知道对象的最终值,并且您可以做一些更有效的事情。

关于性能,仅此而已吗?

并不真地。如果您稍后需要将该对象传递给一个按值获取参数的函数,例如void g(T value),在按指针传递的情况下,调用者的堆栈中有一个命名对象,因此必须将对象复制(或移动)到调用约定需要 value 参数的位置。在按值返回的情况下,知道它会调用的编译器知道g(f())返回的对象的唯一用途f()是作为的参数g(),所以它可以在调用时传递一个指向适当位置的指针f(),这意味着赢了不要完成任何副本。在这一点上,手动方法开始真正落后于编译器的方法,即使实现f使用上面的哑转换!

T obj;    // default initialize
f(&obj);  // assign (or modify in place)
g(obj);   // copy

g(f());   // single object is returned and passed to g(), no copies
于 2013-08-14T17:49:43.893 回答
1

实际上不可能(或不希望)总是通过引用返回一个值(考虑operator+作为一个基本的反例)。

回答您的问题:您通常不依赖或期望 NRVO 总是发生,但您确实希望编译器能够进行合理的优化工作。只有当分析表明复制返回值的成本很高时,您才需要担心帮助编译器提供提示或备用接口。

编辑some function could be optimized just by using return parameter

首先,请记住,如果不经常调用该函数,或者编译器具有足够的智能,则您不能保证 return-by-out-parameter 是一种优化。其次,请记住,您将拥有代码的未来维护者,并且编写清晰、易于理解的代码是您可以提供的最大帮助之一(无论代码损坏的速度有多快)。第三,花点时间阅读http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/看看它是否会改变你的想法。

于 2013-08-14T16:52:34.610 回答
0

许多人认为将非常量引用参数传递给函数,然后在函数中更改这些参数不是很直观。

此外,还有许多按值返回结果的预定义运算符(例如,算术运算符,如operator+operator-等...)。由于您希望保留此类运算符的默认语义(和签名),因此您不得不依赖 NRVO 来优化按值返回的临时对象。

最后,在许多情况下,按值返回比传入要通过非常量引用(或指针)更改的参数更容易链接。

于 2013-08-14T16:51:25.717 回答