const <type>& foo() 与 <type> foo() 的区别

3
在一个非模板类中,是否有理由更喜欢以const <type>& foo();形式返回函数的返回值类型,而不是<type> foo();?这里的<type>是一个内在类型。如果<type>是一个类/结构体对象呢?
此外,我们也关心函数是否被定义为const时是否有所不同:const <type>& foo() const;<type> foo() const; 例如,在一个非模板类的非模板函数中:
const int& foo() const { return member_variable_; }

对比

int foo() const { return member_variable_; }

1
什么时候应该从类方法返回对象的引用? - Stefan Monov
@Stefan,细微的差别,你会注意到我的问题标记为优化...而你找到的另一个问题是关于最佳实践的。 - oz10
5个回答

3
对于基本类型,直接按值返回即可。没有理由通过const引用返回原始类型。
对于非基本类型,则要看情况而定。
如果返回的值是函数的局部变量,则必须按值返回。否则,在调用者可以使用返回的引用之前,该变量将被销毁。
如果返回的值是类数据成员,则可以选择。
通过const引用返回避免了复制,这可能是一个性能优势。然而,它会将你的接口绑定到实现上,即数据成员和返回值必须是相同的类型。它也不够安全,因为调用者可以比对象的生命周期更长地保留引用,例如:
const X& x = y->foo();
delete y;
// use x, oops!

按值返回会产生一到两个副本,这取决于您是否能利用返回值优化(对于像这样的简单get()方法来说,这很可能)。此外,使用默认复制构造函数复制对象的副本可以更有效地复制(想想:编译器可以直接memcpy数据)。

建议:从按值返回开始,仅在确定该调用/副本已知是性能问题后才切换到按const引用返回。

最后,另一种选择是通过参数返回,这对于更复杂的对象(例如字符串)非常有用:

void foo(X& return_val) { return_val = member_variable_; }

或者:

void foo(X* return_val) {
    assert(return_val != 0);
    *return_val = member_variable_;
}

使用默认的...memcpy复制对象 => 并不完全正确。标准允许编译器省略复制,称为返回值优化。T f(){ T tmp; /* 操作 */ return tmp; } 编译器允许的操作是在分配给返回值的内存中创建 tmp 变量,从而避免在返回语句中复制变量。编译器不会将对象从一个位置复制到另一个位置。 - David Rodríguez - dribeas
我的memcpy注释并不是指返回值优化,而是指POD对象的直接复制与间接复制。请参见http://docs.sun.com/app/docs/doc/820-7599/bkahu?a=view作为例子。 - Jason Govig

0
如果你需要返回一个值,你必须返回一个值 - const 引用不是可接受的替代品。但我猜你正在询问这样的事情:
struct A {
   X x;
    ...
   X f1() { return x; }
   const X & f2() { return x; }
};

我从未想出一个百分之百可靠的指南。


@Neil:我对内在类型的情况更感兴趣,以你的例子来说,X将是一个整数。(不值得一提的是,我更喜欢按值返回) - oz10
1
我认为,使用auto关键字后,返回引用变得越来越危险。现在类似于auto x = a.f2()的代码会导致x成为一个静默的引用,如果a被销毁,而x还存在(如果它是一个智能指针就很有可能),那么我们就会得到一个悬垂引用。我个人认为,除非意图是返回引用(最好通过返回[可能是智能的]指针来突出显示),否则就按值返回,让RVO发挥作用。 - Pavel Minaev
1
哦,是的,而且,在C++0x中使用移动语义,对于具有移动构造函数的任何类型,按值返回的性能损失将是可以忽略不计的(这包括所有标准类型,并且定义一个移动构造函数对于任何昂贵的用户自定义类型来说肯定是最佳实践)。 - Pavel Minaev
如果X是一个巨大的结构体,并且您的假设函数可以选择返回成员x或其const引用,那么无论如何使用RVO或移动构造函数都无法避免按值返回需要复制,而按引用(或指针)返回则不需要。如果调用者不希望该值在他下面更改或消失,则可以自己复制(尽管Pavel的第一个观点很有趣,而且我以前从未考虑过-现在可能不那么容易复制)。 - Steve Jessop
@Todd:是的,在这种情况下,移动语义也无法帮助您。另一方面,反对通过引用返回成员的另一个论点是,它以某种方式公开了您的实现,如果您稍后更改它而不破坏客户端,则会导致API中断(由于客户端可以将返回的const引用绑定到自己类中的const引用字段;如果您稍后开始按值返回,则他将绑定到立即超出范围的临时对象)。 - Pavel Minaev
显示剩余4条评论

0

这完全取决于你想要做什么。

如果你想返回一个对象的引用供用户代码使用,则:

<type>& foo()  { ... }

如果你想以只读的方式返回一个对象的引用,仅允许访问该对象类型的const函数,那么:
const <type>& foo() { ... }

如果您想返回一个对象的引用供用户代码使用,即使该类实例被用作只读(如const),则应执行以下操作:

<type>& foo() const { ... }

如果您想以只读方式返回一个对象的引用,仅允许访问此对象类型的const函数,即使类实例以只读方式(作为const)使用,则应:
const <type>& foo() const { ... }

只是要提醒一下:不要返回函数变量的引用,因为它们在函数外部不再存在...

1
我必须强烈反对“话虽如此,对于这种函数来说一个好的做法(但并非所有情况下都是)...”——仅在极少数情况下才是一个好的做法,容器的 operator[] 是我能想到的唯一例子。 - anon
好的,我会删除这个推荐,因为我感觉自己还不够有经验。但是我必须说,我经常使用类似访问器的成员函数来匹配这个方案,利用const-correctness使得整个系统的只读访问成为可能,而不会通过使用非const成员函数来破坏它。话虽如此,我可能还不够有经验,无法看到其中的问题。 - Klaim
仅仅因为你有一个非const实例(或引用)的类,并不意味着你应该无限制地访问它的数据成员。如果你仅仅依赖于正在访问的实例的const属性,那么你可能会放弃成员函数并使用普通结构体。 - anon

0
const <type>& foo();

你的类中可能有一个元素容器资源,foo() 返回特定元素。如果你知道这个资源将长时间存在,你可以返回容器中元素的常量引用,这样可以避免不必要的数据分配/复制。 它可能是const的,也可能不是,这取决于你如何处理数据。如果你不希望通过foo()返回的引用修改数据,则将其设为const。也许你有一个需要修改资源并需要做一些额外工作的情况,那么就将其设为非const。你可以在你的API中拥有两者,用户将根据用例选择使用哪个。
<type> foo();

返回资源<type>的副本。这意味着会创建一个副本。这适用于简单的数据或值可能被共享且您想确保每个<type>实例是单独的 - 不像一些字符串或整数那样是共享的。

0
如果类型是内置类型,传递 const 引用并不会提高性能,反而可能会降低性能,同时也不太安全;最好选择按值传递和返回。我不是很擅长汇编语言,但据我所知,通过引用返回意味着该值无法存储在寄存器中,因为它需要一个返回地址,从而可能禁用一些其他有用的优化;不过我对此不太确定。
如果是类或结构体,则取决于值的生命周期;如果它只存在于函数内部,则无法返回,否则,如果它是类成员,则我更喜欢使用 const type& 返回,因为这样可以避免复制构造函数,从而提高性能(虽然有不同的意见,按值返回被认为更安全)。这还可以避免你必须实现复制构造函数或使用可能存在问题的默认方式,这很方便。

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