对于以下代码:
#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
所以我猜整件事情都不可靠。
答案 0 :(得分:5)
Baz
是一个带有构造函数的类。因此,当您使用列表初始化时,编译器将查找要调用的构造函数。该构造函数将传递braced-init-list的成员,如果匹配列表成员,则传递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
是对生命周期延长的临时子对象的引用。
有些事情你无法通过功能做到。
答案 1 :(得分:0)
要指出VC ++和g ++之间的区别,我认为下面有一些信息 https://stackoverflow.com/a/26581337/4669663
由于默认情况下添加了移动构造函数,因此
Foo:Bar
~Foo:Bar - Move constructor generated by VC
Foo:Baz
~Foo:Baz
“Rvalue references v3.0”添加了新规则以自动生成移动 构造函数和在特定条件下移动赋值运算符。 这是在Visual Studio 2015中实现的。