更新在底部
q1:对于管理相当大量资源的类,您如何实现五规则,但您希望它通过值传递,因为这大大简化和美化了它的使用?或者甚至不需要规则的所有五项?
在实践中,我开始使用 3D 成像,其中图像通常是 128*128*128 双倍。能够写出这样的东西会使数学变得容易得多:
Data a = MakeData();
Data c = 5 * a + ( 1 + MakeMoreData() ) / 3;
q2:使用复制省略/RVO/移动语义的组合,编译器应该能够以最少的复制来实现这一点,不是吗?
我试图弄清楚如何做到这一点,所以我从基础开始;假设一个对象实现了实现复制和赋值的传统方式:
class AnObject
{
public:
AnObject( size_t n = 0 ) :
n( n ),
a( new int[ n ] )
{}
AnObject( const AnObject& rh ) :
n( rh.n ),
a( new int[ rh.n ] )
{
std::copy( rh.a, rh.a + n, a );
}
AnObject& operator = ( AnObject rh )
{
swap( *this, rh );
return *this;
}
friend void swap( AnObject& first, AnObject& second )
{
std::swap( first.n, second.n );
std::swap( first.a, second.a );
}
~AnObject()
{
delete [] a;
}
private:
size_t n;
int* a;
};
现在输入右值并移动语义。据我所知,这将是一个有效的实现:
AnObject( AnObject&& rh ) :
n( rh.n ),
a( rh.a )
{
rh.n = 0;
rh.a = nullptr;
}
AnObject& operator = ( AnObject&& rh )
{
n = rh.n;
a = rh.a;
rh.n = 0;
rh.a = nullptr;
return *this;
}
但是编译器(VC++ 2010 SP1)对此不太满意,编译器通常是正确的:
AnObject make()
{
return AnObject();
}
int main()
{
AnObject a;
a = make(); //error C2593: 'operator =' is ambiguous
}
q3:如何解决?回到 AnObject& operator = ( const AnObject& rh ) 肯定会修复它,但我们不会失去一个相当重要的优化机会吗?
除此之外,很明显移动构造函数和赋值的代码充满了重复。所以现在我们忘记了歧义并尝试使用复制和交换来解决这个问题,但现在是右值。正如这里所解释的,我们甚至不需要自定义交换,而是让 std::swap 完成所有工作,这听起来很有希望。所以我写了以下内容,希望 std::swap 会使用 move 构造函数复制构造一个临时的,然后用 *this 交换它:
AnObject& operator = ( AnObject&& rh )
{
std::swap( *this, rh );
return *this;
}
但这不起作用,而是由于无限递归而导致堆栈溢出,因为 std::swap 再次调用我们的 operator = ( AnObject&& rh )。q4:有人可以提供一个示例来说明示例中的含义吗?
我们可以通过提供第二个交换函数来解决这个问题:
AnObject( AnObject&& rh )
{
swap( *this, std::move( rh ) );
}
AnObject& operator = ( AnObject&& rh )
{
swap( *this, std::move( rh ) );
return *this;
}
friend void swap( AnObject& first, AnObject&& second )
{
first.n = second.n;
first.a = second.a;
second.n = 0;
second.a = nullptr;
}
现在有几乎两倍的金额代码,但是它的移动部分通过允许相当便宜的移动来支付;但另一方面,正常的分配不能再从复制省略中受益。在这一点上,我真的很困惑,再也看不到什么是对和错了,所以我希望在这里得到一些意见..
更新所以似乎有两个阵营:
- 一种说法是跳过移动赋值运算符并继续执行 C++03 教给我们的操作,即编写一个按值传递参数的单个赋值运算符。
- 另一种说法是实现移动赋值运算符(毕竟现在是 C++11)并让复制赋值运算符通过引用获取其参数。
(好的,第三个阵营告诉我使用向量,但这超出了这个假设类的范围。好吧,在现实生活中我会使用向量,并且还会有其他成员,但是由于移动构造函数/分配不会自动生成(还没有?)问题仍然存在)
不幸的是,我无法在现实世界的场景中测试这两种实现,因为这个项目刚刚开始,数据实际流动的方式尚不清楚。所以我简单地实现了它们,为分配等添加了计数器,并运行了几次迭代。此代码,其中 T 是实现之一:
template< class T >
T make() { return T( narraySize ); }
template< class T >
void assign( T& r ) { r = make< T >(); }
template< class T >
void Test()
{
T a;
T b;
for( size_t i = 0 ; i < numIter ; ++i )
{
assign( a );
assign( b );
T d( a );
T e( b );
T f( make< T >() );
T g( make< T >() + make< T >() );
}
}
要么这段代码不够好,无法测试我所追求的,要么编译器太聪明了:不管我对 arraySize 和 numIter 使用什么,两个阵营的结果几乎相同:分配的数量相同,时间上的微小变化,但没有可重现的显着差异。
因此,除非有人能指出更好的测试方法(鉴于实际使用场景尚不清楚),否则我将不得不得出结论,这无关紧要,因此留给开发人员的口味。在这种情况下,我会选择#2。