[英]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
. 但是,实现将忽略从临时
S
到b
的移动。 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;
}
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.