struct S { std::vector<int>&& vec; }; int main() { S s1{std::vector<int>(5)}; // construct with temporary std::cout << s1.vec[0] << '\n'; // fine,temporary is alive }
但是,当S被赋予一个显式的值构造函数时,它不再是一个聚合,并且该方案在s1.vec [0]上无效读取失败,
struct S { std::vector<int>&& vec; S(std::vector<int>&& v) : vec{std::move(v)} // bind to the temporary provided { } }; int main() { S s1{std::vector<int>(5)}; // construct with temporary std::cout << s1.vec[0] << '\n'; // not ok. invalid read on free'd memory }
为什么这是有效的聚合?我认为它与构造函数是一个实际的函数调用有关,基于我用const lvalue引用的红色.另外,有什么办法可以使后一种情况发挥作用吗?
在SO上使用lvalue引用的类似情况有很多问题.我看到,如果我使用了一个const lvalue参考,它不会有助于延长临时的生命周期,rvalue引用的规则是否相同?
解决方法
可以使用聚合初始化来延长临时性的使用寿命,因为用户定义的构造函数实际上是一个函数调用,所以不能这样做.
注意:T const&和T&&适用于总体生命力的情况,延长对他们的临时生活.
什么是聚合?
struct S { // (1) std::vector<int>&& vec; };
为了回答这个问题,我们必须深入了解一个聚合的初始化和一个类类型的初始化之间的区别,但首先我们必须确定一个聚合是什么:
8.5.1p1
Aggregates[dcl.init.aggr]
An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1),no private or protected non-static data members (Clause 11),no base classes (Clause 10),and no virtual functions (10.3)
注意:上述表示(1)是聚合.
如何初始化聚合?
聚合和“非聚合”之间的初始化差异很大,这里直接来自标准:
8.5.1p2
Aggregates[dcl.init.aggr]
When an aggregate is initialized by an initializer list,as specified in 8.5.4,the elements of the initializer list are taken as initializers for the members of the aggregate,in increasing subscript or member order. Each member is copy-initialized from the corresponding initializer-clause.
上述引用表明,我们正在初始化器子句中初始化器初始化我们的聚合成员,之间没有任何一个步骤.
struct A { std::string a; int b; };A x { std::string {"abc"},2 };从语义上来说,上面相当于使用下面的初始化我们的成员,只是这种情况下的A :: a和A :: b只能通过x.a和x.b访问.
std::string A::a { std::string {"abc"} }; int A::b { 2 };如果我们将A :: a的类型更改为rvalue引用或const lvalue引用,我们将直接将初始化的临时用法绑定到x.a.
rvalue引用的规则和const lvalue引用表明,临时生存期将被扩展到主机的生命周期,这正是将要发生的事情.
struct S { // (2) std::vector<int>&& vec; S(std::vector<int>&& v) : vec{std::move(v)} // bind to the temporary provided { } };一个构造函数真的只是一个花哨的函数,用于初始化一个类实例.适用于功能的相同规则适用于它们.
当涉及延长临时人员的终身时间时,没有任何区别.
std::string&& func (std::string&& ref) { return std::move (ref); }临时传递给func将不会延长其生命周期,因为我们有一个声明为rvalue / lvalue引用的参数.即使我们返回“相同”的引用,以便它在func之外可用,它不会发生.
这是在(2)的构造函数中发生的,毕竟构造函数只是一个用于初始化对象的“花式函数”.
12.2p5
Temporary objects[class.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:
A temporary bound to a reference member in a constructor’s ctor-initializer (12.6.2) persists until the constructor exits.
A temporary bound to a reference parameter in a function call (5.2.2) persists until the completion of the full-expression containing the call.
The lifetime of a temporary bound to the returned value in a function return statement (6.6.3) is not extended; the temporary is destroyed at the end of the full-expression in the return statement.
- 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.
注意:请注意,通过新的T {…}的聚合初始化与上述规则不同.