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

  • 本文关键字:返回值 优化 依赖 c++ nrvo
  • 更新时间 :
  • 英文 :


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

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

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

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

给定一个通过指针获取参数的函数,存在一个琐碎的转换,它将生成一个通过值返回的函数,并且编译器可以对其进行琐碎的优化。

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是微不足道的,因为所有的返回语句都引用了同一个对象。

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

完全相同的成本

这超出了语言的范围,但当编译器生成代码时,它遵循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)),则在按指针传递的情况下,调用方的堆栈中有一个命名对象,因此必须将该对象复制(或移动)到调用约定需要值参数的位置,知道将调用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

事实上,总是通过引用返回值是不可能的(或不可取的)(将operator+作为一个基本的反例)。

回答您的问题:您通常不会依赖或期望NRVO总是发生,但您确实期望编译器做合理的优化工作。只有当评测表明复制返回值很昂贵时,您才需要担心使用提示或备用接口来帮助编译器。

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

首先,请记住,如果函数不经常调用,或者编译器有足够的智能,则不能保证out参数的返回是优化的。其次,请记住,您将来会有代码的维护者,编写清晰、可扩展的代码是您所能提供的最大帮助之一(不管代码崩溃的速度有多快)。第三,花点时间阅读http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/看看它是否会改变你的想法。

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

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

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

最新更新