鉴于此代码示例,有关传递给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中删除)
构造函数的ctor-initializer(12.6.2)中对引用成员的临时绑定一直持续到 构造函数退出。
N4296 12.6.2 p8
绑定到mem-initializer中引用成员的临时表达式格式不正确。
因此,拥有像[1]
这样的用户定义构造函数绝对不是我们想要的。它甚至应该在最新的C ++ 14中形成不良(或者是它?)gcc和clang都没有警告它。
它是否随直接聚合初始化而改变?在这种情况下,我看起来像是延长了临时寿命。
现在关于复制初始化,Default move constructor and reference members表示隐式生成[2]
。鉴于移动可能被省略,同样的规则是否适用于隐式生成的移动构造函数?
a, b, c, d
中哪一个有有效参考?
答案 0 :(得分:5)
除非存在特定异常,否则将扩展绑定到引用的临时对象的生命周期。也就是说,如果没有这样的例外,那么寿命将会延长。
从最近的草案中,N4567:
第二个上下文[寿命延长的地方]是a 引用必然是临时的。临时的 引用是绑定的,或者是临时的,是一个完整的对象 引用绑定的子对象在其生命周期内持续存在 引用除外:
- (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
这会创建一个临时std::string
并使用它初始化str_
数据成员。 S a{"foo"}
使用聚合初始化,因此不涉及mem-initializer。生命周期扩展的例外都不适用,因此该临时值的生命周期延长到参考数据成员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
的生命周期。您可以在以下程序中观察到这一点:
#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;
}
请注意,loud
的析构函数在“main of end”之前调用,而x
生效直到该跟踪之后。形式上,临时loud
在创建它的完整表达式结束时被销毁。
如果aggr
的移动构造函数是用户定义的,则行为不会改变。
在C ++ 17中强制使用copy-elision:我们使用lhs S{"bar"}
上的对象识别rhs b
上的对象。这会导致临时的生命周期延长到b
的生命周期。请参阅CWG 1697。
对于剩下的两个例子,移动构造函数 - 如果被调用 - 只是复制引用。当然,移动构造函数(S
)可以省略,但这是不可观察的,因为它只复制引用。