一番下のUPDATE
q1:かなり重いリソースを管理するクラスに 5 のルールをどのように実装しますか? その使用法を大幅に簡素化して美しくするために、値によって渡す必要がありますか? それとも、ルールの 5 つの項目すべてが必要ではないでしょうか?
実際には、画像が通常 128*128*128 double である 3D イメージングで何かを始めています。このようなものを書くことができれば、数学はずっと簡単になります:
Data a = MakeData();
Data c = 5 * a + ( 1 + MakeMoreData() ) / 3;
q2: copy elision / RVO / move セマンティクスの組み合わせを使用すると、コンパイラは最小限のコピーでこれを実行できるはずですよね?
これを行う方法を理解しようとしたので、基本から始めました。コピーと割り当てを実装する従来の方法を実装するオブジェクトを想定します。
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 ) に戻ると確かに修正されますが、かなり重要な最適化の機会を失っていませんか?
それとは別に、move コンストラクターと代入のコードが重複していることは明らかです。したがって、今のところあいまいさを忘れて、コピーとスワップを使用してこれを解決しようとしますが、今度は右辺値を使用します。ここで説明したように、カスタム スワップは必要なく、代わりに std::swap にすべての作業を任せることができます。これは非常に有望に思えます。そこで、std::swap が move コンストラクターを使用して一時的に構成をコピーし、それを *this と交換することを期待して、次のように書きました。
AnObject& operator = ( AnObject&& rh )
{
std::swap( *this, rh );
return *this;
}
しかし、それはうまくいかず、 std::swap が演算子 = ( AnObject&& rh ) を再度呼び出すため、無限再帰によるスタック オーバーフローが発生します。q4:誰かがその例で何を意味するかの例を提供できますか?
これは、2 番目の swap 関数を提供することで解決できます。
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;
}
これでコードの量はほぼ 2 倍になりましたが、その移動部分はかなり安価な移動を許可することで報われます。しかし一方で、通常の代入はもはやコピー省略の恩恵を受けることができません。この時点で、私は本当に混乱しており、何が正しくて何が間違っているのかわからなくなっているので、ここで何らかの情報を得たいと思っています..
更新したがって、2つのキャンプがあるようです。
- 移動代入演算子をスキップして、C++03 で教えられたこと、つまり、引数を値で渡す単一の代入演算子を作成することを続行するようにというものです。
- もう1つは、移動代入演算子を実装し(結局のところ、現在はC ++ 11です)、コピー代入演算子に引数を参照渡しさせるように言っています。
(わかりました、そしてベクトルを使用するように私に言っている3番目のキャンプがありますが、それはこの架空のクラスの範囲外です。実際には私はベクトルを使用し、他のメンバーもいるでしょうが、ムーブコンストラクタ/割り当ては自動的に生成されません (まだ?) 質問はまだ保持されます)
残念ながら、このプロジェクトは開始されたばかりであり、データが実際にどのように流れるかはまだわかっていないため、実際のシナリオで両方の実装をテストすることはできません。そのため、単純に両方を実装し、割り当てなどのカウンターを追加して、約 2 回の反復を実行しました。このコードで、T は実装の 1 つです。
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を選びます。