参考成员总数和临时寿命 [英] Aggregate reference member and temporary lifetime
问题描述
鉴于此代码示例,关于将临时字符串的生存期传递给 S
的规则是什么。
Given this code sample, what are the rules regarding the lifetime of the temporary string being passed to 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
根据标准:
N4140 12.2 p5.1(已在N4296中删除)
N4140 12.2 p5.1 (removed in N4296)
临时绑定到参考成员构造函数的ctor初始化程序(12.6.2)一直持续到
构造函数退出。
A temporary bound to a reference member in a constructor’s ctor-initializer (12.6.2) persists until the constructor exits.
N4296 12.6.2 p8
N4296 12.6.2 p8
绑定到mem初始化程序中引用成员的临时表达式格式错误。
A temporary expression bound to a reference member in a mem-initializer is ill-formed.
因此,拥有用户定义的构造函数,例如 [1]
绝对不是我们想要的。它甚至应该在最新的C ++ 14中格式错误(或者是?),gcc和clang都没有警告它。
直接聚合初始化会改变吗?我看起来在那种情况下,临时生存期得到延长。
So having a user defined constructor like [1]
is definitively not what we want. It's even supposed to be ill-formed in the latest C++14 (or is it?) neither gcc nor clang warned about it.
Does it change with direct aggregate initialization? I looks like in that case, the temporary lifetime is extended.
现在有关复制初始化,默认移动构造函数和引用成员指出 [2]
是隐式生成的。鉴于可能会忽略移动,事实是否同样适用于隐式生成的移动构造函数?
Now regarding copy-initialization, Default move constructor and reference members states that [2]
is implicitly generated. Given the fact that the move might be elided, does the same rule apply to the implicitly generated move constructor?
其中 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.
从最近的草案N4567:
From a fairly recent draft, 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)绑定到函数调用(5.2.2)中的引用参数的临时对象一直存在,直到
full- - (5.2)在函数return语句(6.6.3)中,临时绑定到返回值的临时生存期不会延长;
- (5.3)绑定到new-initializer中的引用的临时对象(5.3.4)。 )一直持续到包含新初始化程序的完整表达式
完成。
正如OP所述,对C ++ 11的唯一重大更改是在C ++ 11中,引用类型的数据成员(来自N3337)还有一个例外:
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):
- 在构造函数的ctor-initializer(12.6.2)中,临时绑定到引用成员的行为一直存在,直到构造函数退出。
此内容已在 CWG 1696 (后C ++ 14),并且通过mem-initializer将临时对象绑定到引用数据成员的格式现在不正确。
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.
关于OP中的示例:
struct S
{
const std::string& str_;
};
S a{"foo"}; // direct-initialization
这将创建一个临时 std :: string
并使用它初始化 str _
数据成员。 S a { foo}
使用聚合初始化,因此不涉及内存初始化。没有任何适用于生存期扩展的例外,因此该临时生存期将扩展为参考数据成员 str _
的生存期。
This creates a temporary std::string
and initializes the str_
data member with it. The S a{"foo"}
uses aggregate-initialization, so no mem-initializer is involved. 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_
.
auto b = S{"bar"}; // copy-initialization with rvalue
强制初始化之前的复制初始化用C ++ 17复制省略项:
形式上,我们创建一个临时 std :: string
,初始化一个临时 S
,将临时 std :: string
绑定到 str _
参考成员。然后,我们将该临时 S
移到 b
中。这将复制引用,不会延长 std :: string
临时文件的寿命。
但是,实现将避免从临时 S
迁移到 b
。但是,这绝对不能影响临时 std :: string
的生存期。您可以在以下程序中观察到这一点:
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. Then, we move that temporary S
into b
. This will "copy" the reference, which will not extend the lifetime of the std::string
temporary.
However, implementations will elide the move from the temporary S
to b
. This must not affect the lifetime of the temporary std::string
though. 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;
}
请注意,大声
的析构函数在 main of end之前被调用,而 x
生存到该跟踪之后。正式地,临时 loud
在创建它的完整表达式的末尾被销毁。
Note that the destructor of loud
is called before the "end of main", whereas x
lives until after that trace. Formally, the temporary loud
is destroyed at the end of the full-expression which created it.
该行为确实如果用户定义了 aggr
的move构造函数,则不会更改。
The behaviour does not change if the move constructor of aggr
is user-defined.
在C ++ 17:我们将rhs S { bar}
上的对象与lhs b上的对象标识
。这导致临时对象的生存期延长到 b
的生存期。参见 CWG 1697 。
With mandatory copy-elision in C++17: We identify the object on the rhs S{"bar"}
with the object on the lhs b
. This causes the lifetime of the temporary to be extended to the lifetime of b
. See CWG 1697.
对于其余两个示例,move构造函数(如果调用)仅复制引用。当然,可以省略( S
的move构造函数),但这不是可观察到的,因为它仅复制引用。
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.
这篇关于参考成员总数和临时寿命的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!