为什么要依靠命名返回值优化?

时间:2013-08-14 16:47:48

标签: c++ nrvo

我正在阅读关于NRVO的文章并试图了解何时应该依赖它而不是何时依赖它。现在我有一个问题:为什么要依靠NRVO呢?始终可以通过引用显式传递返回参数,那么有没有理由依赖NRVO?

4 个答案:

答案 0 :(得分:7)

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

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);

答案 1 :(得分:4)

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

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

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

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

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

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

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

  4. 这个转换后的函数有一些与指针传递相同的缺点,但它永远不会更糟。但它表明,只要通过指针传递是一个选项,按值返回也是一个具有相同成本的选项。

    完全相同的费用?

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

    的时候
    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
    

答案 2 :(得分: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/,看看它是否会改变你的想法。

答案 3 :(得分:0)

许多人认为将非const参考参数传递给函数然后在函数中更改这些参数并不是很直观。

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

最后,按值返回允许在许多情况下更容易链接,而不是通过非const引用(或指针)传递参数。