我一直在尝试编写一个无法复制但可以移动的类,除了使用命名构造函数之外,无法创建该类.我用下面的namedConstructor3实现了我的目标.但是,我不明白为什么namedConstructor2失败了.
struct A
{
int a;
//static A && namedConstructor1( int a_A )
//{
// A d_A( a_A );
// return d_A; // cannot convert from A to A&&
//}
static A && namedConstructor2( int a_A )
{
wcout << L"Named constructor 2\n";
A d_A( a_A );
return move( d_A );
}
static A namedConstructor3( int a_A )
{
wcout << L"Named constructor 3\n";
A d_A( a_A );
return move( d_A );
}
A( A && a_RHS ) : a( a_RHS.a )
{
a_RHS.a = 0;
wcout << L"\tMoved: a = " << a << endl;
}
~A()
{
wcout << L"\tObliterated: a = " << a << endl;
a = -a;
}
A( const A & ) = delete;
A & operator =( const A & ) = delete;
protected:
A( int a_A = 0 ) : a( a_A )
{
wcout << L"\tCreated: a = " << a << endl;
}
};
int main()
{
A d_A2 = A::namedConstructor2( 2 );
A d_A3 = A::namedConstructor3( 3 );
wcout << "Going out of scope\n";
return 0;
}
输出是
Named constructor 2
Created: a = 2
Obliterated: a = 2
Moved: a = -2
Named constructor 3
Created: a = 3
Moved: a = 3
Obliterated: a = 0
Going out of scope
Obliterated: a = 3
Obliterated: a = -2
问题:
>为什么在namedConstructor2中的移动构造函数之前调用析构函数,如输出的第3行和第4行所示?
>为什么被破坏的数据仍然可用于移动的构造函数(由输出的第4行证明)?
>在std :: move的签名导致我认为std :: move的返回值有“两个&&”这个意义上,nameConctor2不是命名为Constructor2“更自然”吗?
template< class T >
typename std::remove_reference<T>::type&& move( T&& t )
用VS2013u4编译.
编辑
Deduplicator的答案让我满意.此编辑是为了完整性.答案和评论表明,namedConstructor3是“次优的”.我补充道
static A namedConstructor4( int a_A )
{
wcout << L"Named constructor 4\n";
A d_A( a_A );
return d_A;
}
到该类和A d_A4 = A :: namedConstructor4(4);到主要功能.新输出(在发布模式下编译,而不是在调试模式下)显示最佳情况甚至不移动对象:
Named constructor 2
Created: a = 2
Obliterated: a = 2
Moved: a = -2
Named constructor 3
Created: a = 3
Moved: a = 3
Obliterated: a = 0
Named constructor 4
Created: a = 4
Going out of scope
Obliterated: a = 4
Obliterated: a = 3
Obliterated: a = -2
最佳答案 std :: move(lvalue或xvalue)不会调用析构函数.
它只是将传递的引用更改为rvalue-reference,因此move-semantics适用.
那么,为什么你的当地太早被摧毁?
简单,返回对局部变量的引用是UB:
Can a local variable’s memory be accessed outside its scope?
逐个遍历您的命名构造函数:
> namedConstructor1返回rvalue-reference.
但是你试图返回一个本地(这是一个左值),并且编译器抱怨该错误.
> namedConstructor2原则上与namedConstructor1相同,但是你从lvalue到rvalue-reference添加了一个显式的强制转换(以std :: move的形式),这会关闭编译器.
因此,在使用返回的rvalue-reference之前,您将获得UB以便向编译器说谎,特别是locals生命周期在函数结束时结束.
> namedConstructor3没问题,虽然不是最佳的.
您正在使用rvalue-reference初始化返回值(不是引用).
次优部分是由于std :: move禁用返回值优化,这将消除实现在这种情况下实际移动(或复制)的需要.