rvalue数据成员初始化:聚合初始化与构造函数

7

以下是代码:

#include <iostream>
#include <memory>
#include <string>
using namespace std;

struct Foo {
    string tag;

    Foo(string t): tag(t){
        cout << "Foo:" << tag << endl;
    }
    ~Foo() {
        cout << "~Foo:" << tag << endl;
    }
};

struct Bar {
    Foo&& foo;
};

struct Baz{
    Foo&& foo;
    Baz(Foo&& f):foo(std::move(f)){

    }
};

int main() {
    Bar bar{Foo("Bar")};
    Baz baz{Foo("Baz")};
    cin.get();
}

结果(g++ 7.1.0):

Foo:Bar
Foo:Baz
~Foo:Baz

我们可以看到,bar 成功地扩展了临时 Foo 的生命周期,但是baz没有成功。这两者之间有什么区别?我该如何正确实现 Baz 的构造函数?

编辑:实际上 VC++2017 给出:

Foo:Bar
~Foo:Bar
Foo:Baz
~Foo:Baz

所以我猜整个事情都不可靠。

在构造函数初始化列表中将临时变量绑定到引用只会将其生命周期延长到该构造函数的末尾。这是文档化行为。实际上,编译器没有存储临时变量的内存空间超过构造函数的返回。 - Igor Tandetnik
在任何阶段无法输出 ~Foo:Bar 将是一个严重的编译器错误。 - M.M
使用g++ 7.1.0编译您的代码,我得到输出的第四行是~Foo:Bar,您能再次检查一下吗? - M.M
@M.M https://wandbox.org/permlink/cUqlBvR6BexveDBX ~Foo:Bar不像~Foo:Baz那样立即被调用。 - songyuanyao
1
@M.M 是的。但我认为OP想知道两种情况的区别,即为什么当整个表达式结束时(临时对象应该被销毁时),~Foo:Bar不会立即调用。 - songyuanyao
显示剩余5条评论
2个回答

5

Baz是一个带有构造函数的类。因此,当您使用列表初始化时,编译器将查找要调用的构造函数。该构造函数将接收花括号初始化列表的成员,或者如果std::initializer_list与列表的成员匹配,则接收它们。在任一情况下,临时绑定到函数参数的规则将生效([class.temporary]/6.1):

一个绑定到函数调用中的引用参数(5.2.2)的临时对象在包含该调用的完整表达式完成之前存在。

然而,Bar不是带有构造函数的类;它是一个聚合体。因此,当您使用列表初始化时,您(在本例中)调用了聚合初始化。因此,该成员引用将直接绑定到给定的prvalue。其规则为([class.temporary]/6):

被引用绑定的临时变量或被引用绑定的子对象的完整临时对象除外,它的持续时间延续至引用的生命周期,但以下情况除外:

随后是3个不适用于这种情况的异常(包括上面引用的6.1)。

Bar::foo的生命周期将延续到main的结束。这直到cin.get()返回才会发生。


如何正确实现Baz的构造函数?

如果您指的是“像Bar”那样“正确”,您是不可能的。聚合体可以做一些非聚合体无法做到的事情;这就是其中之一。

类似于这个:

struct Types { int i; float f };
using Tpl = std::tuple<int, float>;

int &&i1 = Types{1, 1.5}.i;
int &&i2 = std::get<0>(Tpl{1, 1.5});

i2 是对一个已销毁临时变量的子对象的悬空引用。 i1 是对一个被延长生命周期的临时变量的子对象的引用。

有些事情你仅靠函数是无法完成的。


0
为了指出VC++和g++之间的区别,我认为以下链接中有一些相关信息https://dev59.com/VV8d5IYBdhLWcg3wpzlf#26581337
由于移动构造函数是默认添加的,因此这个...
Foo:Bar
~Foo:Bar - Move constructor generated by VC
Foo:Baz
~Foo:Baz

“Rvalue references v3.0” 在特定条件下添加了新规则,自动生成移动构造函数和移动赋值运算符。这一功能已在 Visual Studio 2015 中实现。

这段代码中没有移动操作,因此也没有调用移动构造函数。(除非编译器出现了错误,在标准不允许的情况下在此创建一个临时对象,或者类似这样的情况) - M.M

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接