返回一个常量引用是否更有效率?

15

例如:

这些中哪个是最好的:

std::string f() {} 
或者
const std::string& f() {}

我几周前问了一个几乎相同的问题:https://dev59.com/d3VC5IYBdhLWcg3w9GLM - Rob
不太一样;你的涉及返回成员的引用。在许多情况下,这可能是相对安全的(除非包含对象比引用更早地被销毁),但这个问题更加普遍。 :-) - C. K. Young
这回答解决了你的问题吗?返回一个常量引用而不是副本 - NAND
3个回答

38

函数不应该返回局部对象/变量的引用,因为这些对象在函数返回时会超出其作用域并被销毁。

但是,函数可以返回一个指向作用域不受函数上下文限制的对象的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中使用)引入了右值引用[&&],可用于区分左值和右值引用。通过这种方式,可以实现移动构造函数,有助于返回对象并部分避免复制构造函数和临时对象的析构成本。

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

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

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

它应该接管传递对象的内部所有权,使后者处于默认状态。通过这样做,避免了内部副本并简化了临时对象的销毁。典型的函数工厂将采用以下形式:

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

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


1
我不确定,但我认为你的例子是错误的。你返回了一个局部对象的引用。将你的返回类型更改为简单的“object”。它本身就是一个rvalue,并且将通过优先使用目标对象的移动构造函数移动到其目的地。 - Johannes Schaub - litb

16

我想补充一下尼古拉出色的回答。是的,你决不能返回悬空引用(例如指向局部变量的引用),但在这种情况下有三种有用的方法可以提高性能:

  1. 返回值优化(RVO):您通过值返回,但通过仅使用一个 return 语句创建返回值来消除复制,从而避免复制。以下是使用RVO的示例:How can I tokenize a C++ string?

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

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

  • 顺便说一下,我不能为RVO和NRVO的信息负责;它们直接来自Scott Meyers 的More Effective C++。由于我目前没有书在身边,因此我的描述中有任何错误都是我的问题,而不是Scott的问题。 :-)


    没错 Chris。为了完整回答你的问题,我想提到第四点,即通过即将推出的C++0x中的右值引用已经可用的移动构造函数 :-) - Nicola Bonelli
    @Nicola:由于我对C++0x不是太熟悉,所以我认为你应该写一篇文章介绍移动构造函数的工作原理,我会给你点赞的。 :-) - C. K. Young
    耶!希望你的更新的答案能被接受为最佳答案。 :-) - C. K. Young

    3

    如果你返回对函数内局部变量的引用,那么你可能会遇到问题(取决于编译器及其设置)。

    如果你返回对在函数返回时仍在作用域范围内的实例的引用,则它将更快,因为不需要创建字符串的副本。

    因此,后者在技术上更高效,但根据返回引用的内容,可能无法按预期工作。


    正如我在其他评论中提到的那样,请注意返回值优化和命名返回值优化的工作原理。虽然有时为了正确性必须按值返回,但知道何时应用RVO和NRVO意味着您有时可以消除复制开销。 - C. K. Young
    RVO和NRVO只是编译器优化,当函数返回一个对象时会发生这种优化... - Nicola Bonelli

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接