简体   繁体   English

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

[英]Aggregate reference member and temporary lifetime

Given this code sample, what are the rules regarding the lifetime of the temporary string being passed to S . 鉴于此代码示例,有关传递给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

According to the standard: 根据标准:

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

A temporary bound to a reference member in a constructor's ctor-initializer (12.6.2) persists until the constructor exits. 绑定到构造函数的ctor-initializer(12.6.2)中的引用成员的临时绑定将持续存在,直到构造函数退出。

N4296 12.6.2 p8 N4296 12.6.2 p8

A temporary expression bound to a reference member in a mem-initializer is ill-formed. 绑定到mem-initializer中的引用成员的临时表达式格式不正确。

So having a user defined constructor like [1] is definitively not what we want. 因此,拥有像[1]这样的用户定义构造函数肯定不是我们想要的。 It's even supposed to be ill-formed in the latest C++14 (or is it?) neither gcc nor clang warned about it. 它甚至应该在最新的C ++ 14中形成不良(或者是它?)gcc和clang都没有警告它。
Does it change with direct aggregate initialization? 它是否随直接聚合初始化而改变? I looks like in that case, the temporary lifetime is extended. 在这种情况下,我看起来,临时寿命延长了。

Now regarding copy-initialization, Default move constructor and reference members states that [2] is implicitly generated. 现在关于复制初始化, 默认移动构造函数和引用成员声明隐式生成[2] Given the fact that the move might be elided, does the same rule apply to the implicitly generated move constructor? 鉴于移动可能被省略,同样的规则是否适用于隐式生成的移动构造函数?

Which of a, b, c, d has a valid reference? 哪个a, b, c, d有一个有效的参考?

The lifetime of temporary objects bound to references is extended, unless there's a specific exception. 除非存在特定异常,否则将扩展绑定到引用的临时对象的生存期。 That is, if there is no such exception, then the lifetime will be extended. 也就是说,如果没有这样的例外,那么寿命将会延长。

From a fairly recent draft, N4567: 从最近的草案,N4567:

The second context [where the lifetime is extended] is when a reference is bound to a temporary. 第二个上下文[延长生命周期]是指引用绑定到临时的。 The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference except: 绑定引用的临时对象或绑定引用的子对象的完整对象的临时对象在引用的生命周期内持续存在,除了:

  • (5.1) A temporary object bound to a reference parameter in a function call (5.2.2) persists until the completion of the full-expression containing the call. (5.1)绑定到函数调用(5.2.2)中的引用参数的临时对象将持续到包含该调用的完整表达式完成为止。
  • (5.2) The lifetime of a temporary bound to the returned value in a function return statement (6.6.3) is not extended; (5.2)函数返回语句(6.6.3)中返回值的临时绑定的生存期不会延长; the temporary is destroyed at the end of the full-expression in the return statement. 临时在return语句中的full-expression结束时被销毁。
  • (5.3) A temporary bound to a reference in a new-initializer (5.3.4) persists until the completion of the full-expression containing the new-initializer. (5.3)在new-initializer(5.3.4)中对引用的临时绑定一直持续到包含new-initializer的full-expression完成为止。

The only significant change to C++11 is, as the OP mentioned, that in C++11 there was an additional exception for data members of reference types (from N3337): 正如OP所提到的,对C ++ 11的唯一重大改变是,在C ++ 11中,引用类型的数据成员(来自N3337)还有一个例外:

  • A temporary bound to a reference member in a constructor's ctor-initializer (12.6.2) persists until the constructor exits. 绑定到构造函数的ctor-initializer(12.6.2)中的引用成员的临时绑定将持续存在,直到构造函数退出。

This was removed in CWG 1696 (post-C++14), and binding temporary objects to reference data members via the mem-initializer is now ill-formed. 这在CWG 1696 (后C ++ 14)中被删除,并且通过mem-initializer将临时对象绑定到引用数据成员现在是不正确的。


Regarding the examples in the OP: 关于OP的例子:

 struct S { const std::string& str_; }; S a{"foo"}; // direct-initialization 

This creates a temporary std::string and initializes the str_ data member with it. 这将创建一个临时的std::string并使用它初始化str_ data成员。 The S a{"foo"} uses aggregate-initialization, so no mem-initializer is involved. S a{"foo"}使用聚合初始化,因此不涉及mem-initializer。 None of the exceptions for lifetime extensions apply, therefore the lifetime of that temporary is extended to the lifetime of the reference data member str_ . 生命周期扩展的例外都不适用,因此该临时的生命周期延长到参考数据成员str_的生命周期。


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

Prior to mandatory copy elision with C++17: Formally, we create a temporary std::string , initialize a temporary S by binding the temporary std::string to the str_ reference member. 在使用C ++强制复制省略之前17:正式地,我们创建一个临时的std::string ,通过将临时std::string绑定到str_ reference成员来初始化临时S Then, we move that temporary S into b . 然后,我们将临时S移动到b This will "copy" the reference, which will not extend the lifetime of the std::string temporary. 这将“复制”引用,这不会延长std::string临时的生命周期。 However, implementations will elide the move from the temporary S to b . 但是,实现将忽略从临时Sb的移动。 This must not affect the lifetime of the temporary std::string though. 但这不得影响临时std::string的生命周期。 You can observe this in the following program: 您可以在以下程序中观察到这一点:

#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;
}

Live demo 现场演示

Note that the destructor of loud is called before the "end of main", whereas x lives until after that trace. 请注意,在“主要结束”之前调用loud析构函数,而x直到追踪之后才会生效。 Formally, the temporary loud is destroyed at the end of the full-expression which created it. 正式地,临时loud在创建它的全表达结束时被破坏。

The behaviour does not change if the move constructor of aggr is user-defined. 如果aggr的移动构造aggr是用户定义的,则行为不会更改。

With mandatory copy-elision in C++17: We identify the object on the rhs S{"bar"} with the object on the lhs b . 在C ++ 17中使用强制copy-elision:我们使用lhs b上的对象识别rhs S{"bar"}上的对象。 This causes the lifetime of the temporary to be extended to the lifetime of b . 这导致临时的寿命延长到b的寿命。 See CWG 1697 . CWG 1697


For the remaining two examples, the move constructor - if called - simply copies the reference. 对于其余两个示例,移动构造函数(如果调用)只是复制引用。 The move constructor (of S ) can be elided, of course, but this is not observable since it only copies the reference. 当然,移动构造函数( S )可以省略,但这是不可观察的,因为它只复制引用。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 临时绑定到聚合初始化struct成员的生命周期 - Lifetime of temporary bound to aggregate initialized struct member 匿名结构中的常量引用成员变量是否会延长临时变量的生命周期? - Does a constant reference member variable in an anonymous struct extend the lifetime of a temporary? 传递给存储常量引用的成员的临时对象的生命周期 - Lifetime of temporary objects passed to a member storing a constant reference 通过右值数据成员延长临时的生命周期适用于聚合,但不适用于构造函数,为什么? - Extending temporary's lifetime through rvalue data-member works with aggregate, but not with constructor, why? 绑定到成员初始值设定项列表中的引用成员的临时对象的生命周期(C ++ 14) - Lifetime of a temporary object bound to a reference member in member initializer list (C++14) 使用右值引用延长临时项的生存期 - Extension of the lifetime of a temporary with an rvalue reference 临时引用 vs 临时指针及其生命周期 - Reference to a temporary vs Pointer to temporary and their lifetime 具有临时子表达式的临时子表达式的生命周期,绑定到引用 - Lifetime of a temporary with temporary subexpressions, bound to a reference 参考成员绑定到临时 object,其生命周期将短于构造的 object 的生命周期 - Reference member binds to a temporary object whose life-time would be shorter than the lifetime of the constructed object 引用临时成员 - Reference to a member of temporary
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM