聚合引用成员和临时生命周期

3XX*_*XX0 12 c++ aggregate initialization reference temporary

鉴于此代码示例,有关传递给临时字符串的生命周期的规则是什么S.

struct S
{
    // [1] S(const std::string& str) : str_{str} {}
    // [2] S(S&& other) : str_{std::move(other).str} {}

    const std::string& str_;
};

S a{"foo"}; // direct-initialization

auto b = S{"bar"}; // copy-initialization with rvalue

std::string foobar{"foobar"};
auto c = S{foobar}; // copy-initialization with lvalue

const std::string& baz = "baz";
auto d = S{baz}; // copy-initialization with lvalue-ref to temporary
Run Code Online (Sandbox Code Playgroud)

根据标准:

N4140 12.2 p5.1(在N4296中删除)

绑定到构造函数的ctor-initializer(12.6.2)中的引用成员的临时绑定将持续存在,直到构造函数退出.

N4296 12.6.2 p8

绑定到mem-initializer中的引用成员的临时表达式是错误的.

因此,拥有用户定义的构造函数[1]绝对不是我们想要的.它甚至应该在最新的C++ 14中形成不良(或者是它?)gcc和clang都没有警告它.
它是否随直接聚合初始化而改变?在这种情况下,我看起来,临时寿命延长了.

现在关于复制初始化,默认移动构造函数和引用成员状态[2]是隐式生成的.鉴于移动可能被省略,同样的规则是否适用于隐式生成的移动构造函数?

哪个a, b, c, d有效参考?

dyp*_*dyp 5

除非有特殊的例外,否则绑定到引用的临时对象的生存期将延长。即,如果没有这样的例外,则寿命将延长。

从最近的草案N4567:

第二个上下文(延长生存期的地方)是引用绑定到临时对象时。引用绑定到的临时对象或引用绑定到的子对象的完整对象的临时对象在引用的生存期内一直存在,但以下情况除外:

  • (5.1)绑定到函数调用(5.2.2)中的参考参数的临时对象一直存在,直到包含该调用的完整表达式完成。
  • (5.2)在函数返回语句(6.6.3)中,临时绑定到返回值的生存期不会延长;临时在return语句中的全表达式结束时销毁。
  • (5.3)持久绑定到new-initializer(5.3.4)中的引用,直到包含new-initializer的完整表达式完成。

正如OP所述,对C ++ 11的唯一重大更改是在C ++ 11中,引用类型的数据成员(来自N3337)还有一个额外的例外:

  • 在构造函数的ctor-initializer(12.6.2)中,绑定到引用成员的临时绑定一直存在,直到构造函数退出。

CWG 1696(后C ++ 14)中将其删除,现在通过mem-initializer将临时对象绑定到引用数据成员的格式不正确。


关于OP中的示例:

struct S
{
    const std::string& str_;
};

S a{"foo"}; // direct-initialization
Run Code Online (Sandbox Code Playgroud)

这将创建一个临时文件std::stringstr_使用它初始化数据成员。在S a{"foo"}使用聚合初始化,所以没有MEM-初始化参与。没有任何适用于生存期扩展的例外,因此该临时项的生存期被扩展为参考数据成员的生存期str_


auto b = S{"bar"}; // copy-initialization with rvalue
Run Code Online (Sandbox Code Playgroud)

在使用C ++ 17进行强制复制省略之前: 形式上,我们创建一个临时std::stringS通过将临时绑定std::stringstr_引用成员来初始化一个临时。然后,我们将该临时项S移至中b。这将“复制”引用,不会延长std::string临时文件的寿命。但是,实现将避免从临时迁移Sb。但是,这一定不能影响临时文件的寿命std::string。您可以在以下程序中观察到这一点:

#include <iostream>

#define PRINT_FUNC() { std::cout << __PRETTY_FUNCTION__ << "\n"; }

struct loud
{
    loud() PRINT_FUNC()
    loud(loud const&) PRINT_FUNC()
    loud(loud&&) PRINT_FUNC()
    ~loud() PRINT_FUNC()
};

struct aggr
{
    loud const& l;
    ~aggr() PRINT_FUNC()
};

int main() {
    auto x = aggr{loud{}};
    std::cout << "end of main\n";
    (void)x;
}
Run Code Online (Sandbox Code Playgroud)

现场演示

请注意,de的析构函数loud在“ main的结尾”之前被调用,而x生存直到该跟踪之后。正式地,临时loud在创建它的完整表达式的末尾被销毁。

如果aggr用户的移动构造函数是用户定义的,则行为不会改变。

在C ++ 17中使用强制复制删除:我们将rhs S{"bar"}上的对象与lhs上的对象标识在一起b。这导致临时文件的生存期延长到的生存期b。参见CWG 1697


对于剩下的两个示例,move构造函数(如果调用)仅复制引用。S当然,可以省略()的move构造函数,但这不是可观察到的,因为它仅复制引用。