2013-06-14 18 views
13

MSDNの記事How to: Write a Move Constuctorには、次の推奨事項があります。移動代入演算子を呼び出すことによって移動コンストラクタを実装する

あなたが移動コンストラクタとクラスのための移動代入演算子の両方を提供する場合は、移動代入演算子を呼び出すため 移動コンストラクタを書き込むことによって、冗長なコードを排除することができます。 次の例では、移動代入演算子を呼び出す ムーブコンストラクタの改訂版を示す:

// Move constructor. 
MemoryBlock(MemoryBlock&& other) 
    : _data(NULL) 
    , _length(0) 
{ 
    *this = std::move(other); 
} 

は二重MemoryBlock年代値を初期化することにより、非効率的なこのコードで、またはコンパイラは離れて最適化することができるであろう特別な初期化?移動代入演算子を呼び出すことによって、常に私の移動コンストラクタを記述する必要がありますか?

答えて

15

が、私はそれをこのようにしないだろう。移動メンバーが最初に存在する理由は、のパフォーマンスです。あなたの移動コンストラクターのためにこれを行うことは、スーパーカーのメガバックスを砲撃し、通常のガスを買ってお金を節約しようとしているようなものです。

書き込むコードの量を減らしたい場合は、移動メンバーには書き込まないでください。あなたのクラスは、移動のコンテキストでうまくコピーされます。

コードを高性能にするには、移動コンストラクタを調整し、割り当てをできるだけ早く移動します。良い移動のメンバーは驚異的に速くなり、あなたは荷物、店舗、支店を数えることによって速度を見積もっているはずです。あなたが8の代わりに4つのロード/ストアで何かを書くことができるなら、それをしてください! 1ではなくブランチのないものを書くことができれば、それをやってください!

あなた(またはあなたのクライアントが)std::vectorにあなたのクラスを置く場合は、移動の多くがあなたのタイプに生成され得ることができます。たとえ移動がであっても、8ロード/ストアで高速のを使用しても、4倍または6倍のロード/ストアで2倍、または50%高速化することができます。

個人的に私は待っているカーソルを見るのがうんざりですし、コードを書くのにさらに5分を寄付して、それができるだけ速いことを知っています。

これが価値があると確信していない場合は、両方向に書いて、完全最適化で生成されたアセンブリを調べます。あなたのコンパイラは余分な負荷やストアを最適化するのに十分なほどスマートになるかもしれません。しかし、この時点では、最初に最適化された移動コンストラクタを作成した場合よりも、すでに多くの時間を費やしています。

+12

ここには良いアドバイスがありますが、この回答は、重複コードのコストが2回目の書き込みに費やされる以上のものであることを考慮していないと思います。可読性、保守性、堅牢性よりもはるかに重要なコストがかかります。具体的には、コードが重複している場合、非常に一般的なタイプのバグ、すなわちコードの1つのインスタンスで何かが修正または変更された場合、他のインスタンスではなくバグが発生します。私が人生で気にするよりも多くの時間で噛まれたことで、証明された非常に強力な理由がない限り、私はコードを複製しません。 –

+2

パフォーマンスは理由の1つですが、唯一または最も重要なものではありません。私のクラスはリソースの一意の所有権を持っているため、ほとんどの場合、移動コンストラクタと移動代入演算子を記述します。しばしばパフォーマンスは問題ではないので、重複を避けることは理にかなっています。 – opetroch

+0

@ user779446:ご協力いただきありがとうございます。 http://howardhinnant.github.io/coding_guidelines.html –

1

パフォーマンスの大幅な違いに気付かないと思います。移動代入演算子を移動コンストラクターから使用することをお勧めします。

しかし、私はむしろ、STDを使用します::前方代わりのstd ::動きを、それはより論理的だから:

*this = std::forward<MemoryBlock>(other); 
+3

'std :: forward'はあなたがオブジェクトを動かせるかどうかわからないときに、テンプレートでの使用を意図しています。 – aschepler

+0

"他の"変数は既に値ではないので、std :: moveの必要はありませんか? –

+3

@MikeFisherいいえ、名前付きの値は左辺です – TiMoch

0

これは、移動代入演算子の機能によって異なります。あなたがにリンクされている記事の1を見れば、あなたは、部分的に参照してください。

// Free the existing resource. 
    delete[] _data; 

したがって、この文脈では、あなたが最初_dataを初期化せずに移動コンストラクタから移動代入演算子を呼び出した場合、あなたは希望初期化されていないポインタを削除しようとします。したがって、この例では非効率的であるかどうかは分かりませんが、実際には値を初期化することが重要です。

+0

イニシャライザリスト':_data(other._data) 'を使用した場合は、値を2回初期化する必要はありません。 –

+0

いいえ、代入演算子を呼び出すのではなく、移動コードを再実装しています。 –

2

MemoryBlockクラスのマイC++ 11バージョン。正しいC++コンパイラで11

#include <algorithm> 
#include <vector> 
// #include <stdio.h> 

class MemoryBlock 
{ 
public: 
    explicit MemoryBlock(size_t length) 
    : length_(length), 
     data_(new int[length]) 
    { 
    // printf("allocating %zd\n", length); 
    } 

    ~MemoryBlock() noexcept 
    { 
    delete[] data_; 
    } 

    // copy constructor 
    MemoryBlock(const MemoryBlock& rhs) 
    : MemoryBlock(rhs.length_) // delegating to another ctor 
    { 
    std::copy(rhs.data_, rhs.data_ + length_, data_); 
    } 

    // move constructor 
    MemoryBlock(MemoryBlock&& rhs) noexcept 
    : length_(rhs.length_), 
     data_(rhs.data_) 
    { 
    rhs.length_ = 0; 
    rhs.data_ = nullptr; 
    } 

    // unifying assignment operator. 
    // move assignment is not needed. 
    MemoryBlock& operator=(MemoryBlock rhs) // yes, pass-by-value 
    { 
    swap(rhs); 
    return *this; 
    } 

    size_t Length() const 
    { 
    return length_; 
    } 

    void swap(MemoryBlock& rhs) 
    { 
    std::swap(length_, rhs.length_); 
    std::swap(data_, rhs.data_); 
    } 

private: 
    size_t length_; // note, the prefix underscore is reserved. 
    int* data_; 
}; 

int main() 
{ 
    std::vector<MemoryBlock> v; 
    // v.reserve(10); 
    v.push_back(MemoryBlock(25)); 
    v.push_back(MemoryBlock(75)); 

    v.insert(v.begin() + 1, MemoryBlock(50)); 
} 

MemoryBlock::MemoryBlock(size_t)は唯一のテストプログラムで3回呼び出さなければなりません。

+1

配列を手作業で削除する代わりにstd :: unique_ptr を使用すると、より多くのC++ 11ishになります – TiMoch

+0

移動割り当て演算子でswap()が好きです。これはデストラクタが適切になることを保証しますオブジェクトのインスタンス上で実行され、すべてのリソースを解放します。 –

0

私は単にこれは、常に移動割り当て例外をスローしない限り動作します、そしてそれは一般的にないメンバーの初期化を排除し、書き込み、

MemoryBlock(MemoryBlock&& other) 
{ 
    *this = std::move(other); 
} 

う!このスタイルの

利点:それは異なる環境で異なる場合があるため

  1. あなたは、コンパイラが二重にメンバーを初期化するかどうかを心配する必要はありません。
  2. コードは少ないです。
  3. 今後、クラスに余分なメンバーを追加しても、更新する必要はありません。
  4. コンパイラは移動割り当てをインライン展開することが多いため、コピーコンストラクタのオーバーヘッドは最小限に抑えられます。

@ハワードの投稿は、この質問にはあまり答えていないと思います。実際には、クラスはしばしばコピーを好まず、クラスの多くは単純にコピーコンストラクターとコピー割り当てを無効にします。しかし、ほとんどのクラスはコピー可能でなくても移動可能です。

関連する問題