【问题标题】:Is it more efficient to return a const reference返回 const 引用是否更有效
【发布时间】:2008-11-09 10:06:38
【问题描述】:

例如

这些中什么是最好的:

std::string f() {} 

const std::string& f() {}

【问题讨论】:

标签: c++ performance


【解决方案1】:

函数不应返回对本地对象/变量的引用,因为此类对象超出范围并在函数返回时被销毁。

不同的是,该函数可以返回一个 const 或非 const 引用,该引用的范围不受函数上下文的限制。典型例子是自定义operator<<

std::ostream & operator<<(std::ostream &out, const object &obj)
{
   out << obj.data();
   return out;
}

不幸的是,按值返回有其性能缺陷。正如 Chris 所提到的,按值返回对象涉及临时对象的副本及其随后的销毁。复制通过复制构造函数或operator= 进行。为了避免这些低效率,智能编译器可能会应用 RVO 或 NRVO 优化,但在某些情况下它们不能——多次返回。

即将发布的 C++0x 标准(在 gnu gcc-4.3 中部分可用)引入了右值引用 [&&],可用于区分左值和右值引用。通过这种方式,可以实现 move 构造函数,这有助于部分返回一个对象,从而避免复制构造函数的成本和临时的析构函数。

移动构造函数基本上是 Andrei 几年前在 Chris 建议的文章 http://www.ddj.com/database/184403855 中所设想的。

移动构造函数具有以下签名:

// move constructor
object(object && obj)
{}

并且它应该取得传递对象内部的所有权,使后者处于默认状态。通过这样做,避免了内部结构的副本,并且使临时结构的破坏变得容易。典型的函数工厂将具有以下形式:

object factory()
{
    object obj;
    return std::move(obj);
}

std::move() 从对象返回右值引用。最后但并非最不重要的一点是,移动构造函数允许按右值引用返回不可复制的对象。

【讨论】:

  • 我完全不确定,但我认为你的例子是错误的。您返回对本地对象的引用。将您的返回类型更改为简单的“对象”。它本身已经是一个右值,并且将通过首选目标对象移动构造函数移动到它的目标。
【解决方案2】:

我想补充一下 Nicola 的出色回答。是的,您绝不能返回悬空引用(例如,对局部变量的引用),但是在这些情况下,有三种有用的方法可以提高性能:

  1. 返回值优化 (RVO):您按值返回,但通过只有一个 return 语句来消除复制,这会在现场创建返回值。以下是使用 RVO 的示例:How can I tokenize a C++ string?

  2. 命名返回值优化 (NRVO):按值返回,并在函数顶部首先声明返回值变量。所有return 语句都返回该变量。对于支持 NRVO 的编译器,该变量在返回值槽中分配,并且在返回时不会被复制。例如,

    string
    foobar()
    {
        string result;
        // fill in "result"
        return result;
    }
    
  3. 使用shared_ptr等作为返回类型;这需要在堆而不是堆栈上创建对象。这可以防止悬空引用问题,同时仍然不需要复制整个对象,只需复制智能指针。

顺便说一句,关于 RVO 和 NRVO 的信息,我不能相信;他们直接来自 Scott Meyers 的More Effective C++。因为我现在没有这本书,所以我描述中的任何错误都是我做的,而不是斯科特的。 :-)

【讨论】:

  • 对,克里斯。为了完成你的回答,我希望你提到 4. 移动构造函数,它已经通过即将到来的 c++0x 的右值引用可用:-)
  • @Nicola:由于我对 C++0x 不太熟悉,我认为你应该写一篇文章来说明移动构造函数的工作原理,我会支持你。 :-)
  • 耶!好吧,希望您的更新答案被接受为最佳答案。 :-)
【解决方案3】:

如果您返回对函数本地变量的引用,那么您最终会遇到问题(取决于编译器及其设置)。

如果在函数返回时返回对仍在作用域内的实例的引用,那么它会更快,因为不会创建字符串的副本。

因此后者效率更高(技术上),但可能无法按预期运行,具体取决于您返回的引用。

【讨论】:

  • 正如我在其他 cmets 中提到的,请注意返回值优化和命名返回值优化是如何工作的。虽然有时您必须按值返回以确保正确性,但知道 RVO 和 NRVO 何时适用意味着您有时可以消除复制开销。
  • RVO 和 NRVO 只是在函数返回对象时发生的编译器优化...
猜你喜欢
  • 2010-11-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-28
  • 2014-02-15
相关资源
最近更新 更多