通过右值数据成员来扩展临时对象的生存期,可以使用聚合,但不能使用构造函数,为什么?

问题描述 投票:12回答:1

我发现以下方案可以延长临时生命周期,我不知道是否应该,但是可以。

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 ref读取的内容。另外,有什么方法可以使后一种情况起作用?

在SO上使用左值引用有很多问题处理类似情况。我看到,如果我使用了const lvalue ref,则无助于延长临时文件的寿命,rvalue refs的规则是否相同?

c++ c++11 language-lawyer rvalue-reference object-lifetime
1个回答
10
投票

TL; DR

聚合初始化可用于延长临时对象的寿命,用户定义的构造函数不能做同样的事情,因为它实际上是函数调用。

T const&T&&aggregate-initalization的情况下均适用,并延长了绑定到它们的临时对象的寿命。



什么是Aggregate

struct S {                // (1)
  std::vector<int>&& vec;
};

要回答这个问题,我们将不得不深入研究aggregate的初始化和class type的初始化之间的区别,但是首先我们必须确定aggregate是什么:]]

8.5.1p1 聚合

[dcl.init.aggr]

一个aggregate

是一个数组或一个类(第9条),没有用户提供的构造函数(12.1),没有私有或受保护的非静态数据成员(第11条),没有基类(第10条),并且没有虚拟功能(10.3)

:上面的意思是((1)是一个集合。

如何初始化[[聚合?

aggregate

和“ non-aggregate”之间的初始化有很大的不同,这里直接从标准中引出另一部分:
8.5.1p2

聚合

[dcl.init.aggr]
按8.5.4中的指定,通过初始化程序列表初始化集合时,

初始化程序列表的元素被用作集合成员的初始化程序] >>,按递增的下标或成员顺序。每个成员都是从相应的[[initializer-clause

。中的copy-initialized”。
以上引述表示我们正在使用

initializer-clause中的初始化程序来初始化

aggregate的成员,两者之间没有任何步骤。

struct A { std::string a; int b; };

A x { std::string {"abc"}, 2 };

在语义上,等效于使用以下内容初始化我们的成员,只是在这种情况下,A::aA::b仅可通过x.ax.b访问。

std::string A::a { std::string {"abc"} }; int A::b { 2 };

如果将A::a的类型更改为右值引用或

const左值引用

,我们将

直接

将用于初始化的临时用途绑定到x.arvalue-references]和

const lvalue-references]的规则说,临时生存期将延长到主机的生存期,这正是即将发生的事情。


使用用户声明的构造函数的初始化有何不同?

struct S { // (2) std::vector<int>&& vec; S(std::vector<int>&& v) : vec{std::move(v)} // bind to the temporary provided { } };

A
constructor

实际上只是一个花哨的函数,用于初始化

class

实例。适用于功能的相同规则也适用于它们。 关于延长临时人员的寿命没有区别。std::string&& func (std::string&& ref) { return std::move (ref); }

仅传递给func的临时变量,因为我们有一个声明为右值/左值引用的参数,因此不会延长其寿命。即使我们返回

“ same”

引用,以便它在func之外可用,也不会发生。

这是(2)

的构造函数中发生的事情,毕竟

constructor只是用于初始化对象的“ fancy function

”。

12.2p5

临时对象
[class.temporary]

引用所绑定的临时对象或引用所绑定的子对象的完整对象的临时对象在引用的生存期内一直存在,但以下情况除外:

    与构造函数的ctor-initializer(12.6.2)中的引用成员的临时绑定一直存在,直到构造函数退出。
  • 与函数调用(5.2.2)中的参考参数的临时绑定一直持续到包含该调用的完整表达式完成。

    临时绑定到函数return语句(6.6.3)中返回值的生存期;临时在return语句中的全表达式结束时销毁。

    • new-initializer
  • 中的引用的临时绑定一直存在,直到包含
  • new-initializer的完整表达式完成为止。
:请注意,通过new T { ... }进行的聚合初始化与前面提到的规则不同。
© www.soinside.com 2019 - 2024. All rights reserved.