本周小贴士#107:引用生命周期的扩展

Posted -飞鹤-

tags:

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

作为totw/107最初发表于2015年12月10日

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

在TotW 101之后,有一些关于引用和生命周期混淆的报告,因此在本贴士中,我们将深入地探讨这个问题,“引用生命周期延长何时适用?”

string Foo::GetName();

const string& name = obj.GetName();  // 这是安全/合法的吗?

简而言之,临时对象(所指对象)的生命周期会延长,当且仅当

  • 本地const T&(或T&&,尽管谷歌风格通常忽略它)被初始化为返回临时T或临时变量的T子对象(例如包含T的结构体)的表达式(通常是函数调用)的结果。

解包标准可能有点棘手,因此让我们讨论一些边缘情况来澄清:

  • 当分配给T&时,这不起作用,它必须是const T&。(这是一个编译错误)
  • 如果有(非多态)类型生效,那么它将不起作用。例如,从string赋值给const absl::string_view&不会延长字符串的生命周期。(首先,你也不需要const absl::string_view&,但是这一个单独的问题)。
  • 当你间接获取子对象时,这不起作用:编译器不会查看函数调用(getter或类似的)。子对象形式仅在你直接从一个临时对象的公共成员变量子对象进行赋值时才有效(所以它不会经常出现,因为我们没有很多返回临时结构体的表达式。)
  • 允许类型转换的情况是当T是U的父类时,从U的临时变量赋值给T&。请不要这样做:这比其他情况更让读者困惑。

如果临时变量的生命周期得到延长,那么它将持续到引用超出作用域。如果临时变量的生命周期没有以上述情形延长,那么被引用的T会在语句结束时销毁(当我们到达接下来的;时)。

根据TotW 101,在引用初始化明确的情况下,你可能不应该依赖生命周期的延长:它不会给你带来很多/任何的性能,并且它是微妙的、脆弱的,并且容易为你的审阅者和未来的维护者带来额外的工作。

在一些微妙的情况下,生命周期的扩展正在发生且是必须和有效的(如针对临时容器的ranged-for),但同样,扩展只是临时表达式的结果,而不是子表达式。例如,这些可行:

std::vector<int> GetInts();
for (int i : GetInts()) { }  // 在vector上的生命周期的扩展是重要的

// 在这个字符串返回针对字符的大小为1的string_view
std::vector<absl::string_view> Explode(const string& s);

// 在vector上的生命周期延长起作用,但是在临时字符串“不”
for (absl::string_view s : Explode(StrCat("oo", "ps"))) { }  // 错误

这不可行:

MyProto GetProto();

// 此处的生命周期延长“不起作用”:sub_protos(重复字段)
// 在MyProto超过作用域时被销毁,并且生命周期延长规则
// 在些处来魔法般延长GetProto()返回的MyProto的生命周期是不生效的。
// 子对象生命周期的扩展仅适用于简单的属于其中一部分的关系:
// 编译器看不到sub_protos()本身返回对外部临时子对象的引用
for (const SubProto& p : GetProto().sub_protos()) { }  // 错误

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

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

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

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

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

本周小贴士#126: ‘make_unique‘是新的‘new‘

本周小贴士#141:注意隐式转换到bool