【问题标题】:const reference to member of temporary object [duplicate]对临时对象成员的常量引用[重复]
【发布时间】:2016-11-08 15:34:27
【问题描述】:

C++ 的known feature const 引用延长了从函数返回的临时对象的生命周期,但是对从函数返回的临时对象的成员使用常量引用是否可以接受?

示例:

#include <string>

std::pair<std::string, int> getPair(int n)
{
    return {std::to_string(n), n};
}

int main(int, char*[])
{
    const int x = 123456;
    const auto& str = getPair(x).first;
    printf("%d = %s\n", x, str.c_str());    
    return 0;
}

输出:

123456 = 123456

【问题讨论】:

  • 恕我直言,像“......是一个众所周知的功能......”这样的短语不是很好。他们歧视那些不知道该功能并且不携带任何信息的人。否则,有趣的问题
  • 我不是 100% 确定,但我认为按照标准它是有效的。临时对象的生命周期应延长为其成员访问的生命周期(在这种情况下为str)。也就是说,您应该可以通过复制获取返回值。 RVO 将避免做额外的副本。
  • 我认为这个答案与当前标准相比已经过时了

标签: c++ c++11 reference constants language-lawyer


【解决方案1】:

是的,这个代码是完全可以接受的。根据标准,规则是([class.temporary]):

  1. 临时对象在两种情况下被销毁 与完整表达式的结尾不同的点。第一个上下文 是调用默认构造函数来初始化 大批。如果构造函数有一个或多个默认参数,则 在默认参数中创建的每个临时对象的销毁是 在构造下一个数组元素(如果有)之前排序。

  2. 第二个上下文是引用绑定到临时对象时。这 引用绑定到的临时对象或 作为临时对象的临时对象 引用所绑定的子对象的完整对象 在引用的生命周期内持续存在...

如您所见,突出显示的行清楚地表明对子对象的绑定引用是可以接受的,因为完整的对象也必须延长其生命周期。

请注意,first 确实有资格作为子对象 [intro.object]:

  1. 对象可以包含其他对象,称为子对象。子对象可以 是成员子对象(9.2)、基类子对象(第 10 条)或 数组元素。不是任何其他对象的子对象的对象 称为完整对象。

【讨论】:

  • 我 99% 确定突出显示的文本专门指将父类引用绑定到子类临时对象。
  • @MarkB 你为什么这么认为?
  • @MarkB - 查看我的更新。我已经包含了子对象的定义
  • 这里,first 是子对象,pair 是完整对象。规则说“引用绑定的子对象(即first)的完整对象(即pair)也必须具有生命周期扩展
  • 我并不是说标准表明.first 是一个临时对象。我是说标准在这个词上是模棱两可的。事实上,它在没有正式定义的情况下反复引用它。但是,如果临时对象的子对象 not 本身是临时对象,那么我看不出在什么情况下我上面的答案中突出显示的规则部分可能适用,因此要点的所述部分规则。
【解决方案2】:

定义明确。

来自标准:$12.2/6 Temporary objects [class.temporary]

(强调我的)

引用绑定的临时对象或被绑定的临时对象 引用绑定到的子对象的完整对象 在引用的生命周期内持续存在

关于subobect$1.8/2 The C++ object model [intro.object]

(强调我的)

对象可以包含其他对象,称为子对象。子对象可以 是一个成员子对象([class.mem]),一个基类子对象(子句 [class.derived]) 或数组元素。不是任何其他对象的子对象的对象称为完整对象

first绑定引用,是std::pair的成员子对象,所以临时的std::pair(即完整对象)的生命周期会延长,代码应该没问题。

仅供参考:ClangGCC 说是,VC 说不是。

【讨论】:

  • 但是first是子对象的完整对象吗?
  • @NathanOliver 作为子对象的完整对象的临时...不是first
  • 看起来像 gcc 错误。见open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html。 “1834。常量初始化绑定对 xvalue 的引用”。我认为first 这是一个极值。
  • GCC 尚未实现 CWG 1834 - 如果您尝试使用非整数类型进行相同操作(以避免持续崩溃),您会在 gcc 中看到相同的行为。
  • @Barry 和 Arunmu,你是对的。很高兴知道这一点。
【解决方案3】:

正如我在评论中提到的:

临时的生命周期应该延长,只要 其成员访问的生命周期(在本例中为 str)。也就是说,你 通过复制获取返回值应该没问题。 RVO 会避免做 额外的副本。

来自标准,第 12.2.5 节:

第二个上下文是引用绑定到临时的。这 引用绑定到的临时对象或临时对象 引用绑定到的子对象的完整对象 在引用的生命周期内持续存在,除了:

- 临时绑定到构造函数的引用成员 ctor-initializer (12.6.2) 一直持续到构造函数退出。

——在函数调用中临时绑定到引用参数 (5.2.2) 一直持续到完整表达式完成 包含调用。

为了避免任何麻烦,我宁愿这样做:

auto m_p = getPair(x);

因为RVO 对于这种情况,每个编译器都必须这样做,所以这是尽可能高效的。

【讨论】:

  • 引用绑定的临时对象或引用绑定的子对象的完整对象的临时对象在引用的生命周期内持续存在,除非关于班级成员,这就是first
  • 在这种情况下first 不是pair 的子对象吗?
  • 我很确定它在谈论const base&amp; foo = deriveied_temporary()。在这里。
  • @NathanOliver - 请参阅 [intro.object/2](根据我的回答)。这里first确实是一个子对象。
  • @NathanOliver 是的,我同意 Smeeheey 的观点。这就是它的样子。你能说出你为什么不这么认为吗?
【解决方案4】:

这似乎在 12.2/4-5 中得到解决:

临时对象在两种情况下被销毁 与完整表达式的结尾不同的点。首先 上下文... [处理数组的东西]

第二个上下文是引用绑定到临时的。这 引用绑定到的临时对象或临时对象 引用绑定到的子对象的完整对象 在引用的生命周期内持续存在,除了:

在处理引用成员的构造函数绑定、函数调用、通过引用返回的函数以及在 new-initializers 中绑定的引用有四个例外。

这些情况都不适用,因此临时在完整语句的末尾被销毁,留下对临时成员的悬空引用。

只是帮助编译器意识到它可以从临时移动:const auto str = std::move(getPair(x).first);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-29
    • 1970-01-01
    相关资源
    最近更新 更多