本周小贴士#101:返回值,引用和生命周期

Posted -飞鹤-

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了本周小贴士#101:返回值,引用和生命周期相关的知识,希望对你有一定的参考价值。

作为totw/101最初发表于2015年7月29日

由Titus Winters (titus@google.com)创作

考虑下面的代码片段:

const string& name = obj.GetName();
std::unique_ptr<Consumer> consumer(new Consumer(name));

我尤其想让你注意&。它合适吗?我们应该检查什么?会出什么问题?我发现相当多的C++程序员对引用并不完全清楚,但是通常知道他们应该“避免拷贝”。相对于C++中的大多数问题,它比那更复杂。

逐个分析:返回什么以及如何存储?此处有两个(或三个)重要的问题:

  1. 返回的(在本例中,通过GetName())是什么类型?
  2. 我们存储/初始化的是什么类型(在本例中,名称的类型是什么)?
  3. 如果我们返回一个引用,引用返回的对象是否有生命周期限制?

我们将继续以string作为我们的示例类型,但相同的参数适用于大多数有意义的值类型。

  1. 返回string,初始化string:这通常是RVO,并且在最坏情况下保证是针对现代类型的移动(参见TotW77)。
  2. 返回string&或const string&,初始化string:这是一个副本(我们返回的引用必须是长生存期对象,因此一旦我们初始化一个新string,这里就有两个名字,因此是一个副本。参见TotW77)。有时这很有价值,如同你需要使你的string通过函数提供生命周期保持更长久。
  3. 返回string,初始化string&:这不能编译,因为你不能将引用绑定到临时对象。
  4. 返回const string&,初始化string&:这不能编译,因为你不恰当地删除const。
  5. 返回cosnt string&,初始化cosnt string&:这是没有开销的(你实质上只是返回一个指针)。然而,你已经继承所有的现存的生命周期的限制:该引用生存期多长?大多数访问器方法返回一个成员,引用的有效性与对象的生命一致。
  6. 返回string&,初始化string&:这与#5相同,但是有一个额外的警告:返回的引用是非const,因此对你的引用进行的任何修改都将反映在源中。
  7. 返回string&,初始化const string&:这与#5相同。
  8. 返回string,初始化const string&:你会认为,鉴于#3,这行不通。但是,对此语言有特殊的支持:如果你使用临时对象初始化cosnt T&,T(在本例string中)在引用超过作用域之前不会销毁(在自动或静态变量的常见情况下)。
    方案#8允许完全自反的引用(即”哦,我不想复制,所以我只是分配给一个引用“,而不必考虑返回的内容)。然而,由于#1,它也不会为你做任何事:可能一开始就没有副本。此外,现在你的代码阅读者必须苦于应付你的局部变量,这些变量属于const string&类型,而不是string,因此担心底层string是否超过作用域或更改。
    换句话说:当代码审查原始片段时,我必须担心:
  • GetName()是按值返回还是按引用返回?
  • Consumer的构造函数是采用string,const string&或string_view?
  • 构造函数是否对参数有任何生命周期的要求?(如果它不仅仅是string.)
    然而,如果你首先将一个名字声明为string,它通常依然是有效(因为RVO和移动语义),并且至少在对象生命周期方面是安全的。

此外,如果这里有一个对象生命周期问题,在存储string时通常很容易发现:而不是查看GetName()返回引用的生命周期承诺与SetName()的生命周期要求之间的作用,拥有你的string意味着只需要查看局部代码和SetName()。

所有这些表明:避免拷贝是可行的,只要你不让事情更复杂。在一开始没有拷贝的情况下,使代码更复杂并不是一个好的权衡。

以上是关于本周小贴士#101:返回值,引用和生命周期的主要内容,如果未能解决你的问题,请参考以下文章

本周小贴士#120:返回值是不可触碰的

每周小贴士#149:对象生命周期与=delete

每周小贴士#149:对象生命周期与=delete

本周小贴士#116: 保留对参数的引用

本周小贴士#93:使用absl::Span

本周小贴士#55:名字计数与unique_ptr