9

私は c++ を学んでおり、最近 (ここではスタック オーバーフローで) コピー アンド スワップ イディオムについて学びましたが、それについていくつか質問があります。たとえば、コピー アンド スワップ イディオムを使用する次のクラスがあるとします。

class Foo {
private:
  int * foo;
  int size;

public:
  Foo(size_t size) : size(size) { foo = new int[size](); }
  ~Foo(){delete foo;}

  Foo(Foo const& other){
    size = other.size;
    foo = new int[size];
    copy(other.foo, other.foo + size, foo);
  }

  void swap(Foo& other) { 
    std::swap(foo,  other.foo);  
    std::swap(size, other.size); 
  }

  Foo& operator=(Foo g) { 
    g.swap(*this); 
    return *this; 
  }

  int& operator[] (const int idx) {return foo[idx];}
};

私の質問は、データとして Foo オブジェクトを持つが、カスタムのコピーや割り当てが必要なポインターやその他のリソースを持たない別のクラスがあるとします。

class Bar {
private:
  Foo bar;
public:
  Bar(Foo foo) : bar(foo) {};
  ~Bar(){};
  Bar(Bar const& other) : bar(other.bar) {}; 
  Bar& operator=(Bar other) {bar = other.bar;}
};

今、私は一連の質問があります:

  1. クラスに対して上記で実装されたメソッドとコンストラクターはBar安全ですか? コピーアンドスワップを使用してFoo、割り当てまたはコピー時に害がないようにしBarますか?

  2. コピーコンストラクターとスワップで引数を参照渡しすることは必須ですか?

  3. の引数がoperator=値渡しされると、この引数に対してコピー コンストラクターが呼び出されて、オブジェクトの一時的なコピーが生成され、次に と交換されるのはこのコピーであると言うのは正しい*thisでしょうか? 参照で渡した場合operator=、大きな問題が発生しますよね?

  4. このイディオムがコピーと代入で完全な安全性を提供できない状況はありFooますか?

4

3 に答える 3

8

可能な限り、イニシャライザリストでクラスのメンバーを初期化する必要があります。これで、コメントで説明したバグも処理されます。これを念頭に置いて、コードは次のようになります。

class Foo {
private:
  int size;
  int * foo;

public:
  Foo(size_t size) : size(size), foo(new int[size]) {}
  ~Foo(){delete[] foo;} // note operator delete[], not delete

  Foo(Foo const& other) : size(other.size), foo(new int[other.size]) {
    copy(other.foo, other.foo + size, foo);
  }

  Foo& swap(Foo& other) { 
    std::swap(foo,  other.foo);  
    std::swap(size, other.size); 
    return *this;
  }

  Foo& operator=(Foo g) { 
    return swap(g); 
  }

  int& operator[] (const int idx) {return foo[idx];}
};

class Bar {
private:
  Foo bar;
public:
  Bar(Foo foo) : bar(foo) {};
  ~Bar(){};
  Bar(Bar const& other) : bar(other.bar) { }
  Bar& swap(Bar &other) { bar.swap(other.bar); return *this; }
  Bar& operator=(Bar other) { return swap(other); }
}

全体で同じイディオムを使用します

ノート

質問へのコメントで述べたように、 'カスタムコピーコンストラクターなどは不要ですが、他のものもあるBarと仮定します:-)Bar

2番目の質問

swap両方のインスタンスが変更されているため、への参照による受け渡しが必要です。

値を渡す場合はコピーコンストラクターを呼び出す必要があるため、コピーコンストラクターへの参照による受け渡しが必要です。

3番目の質問

はい

4番目の質問

いいえ、しかしそれは常に物事を行うための最も効率的な方法ではありません

于 2011-05-06T00:38:03.320 に答える
5

1 - Bar クラスに対して上記で実装されたメソッドとコンストラクタは安全ですか? Foo にコピー アンド スワップを使用したことで、Bar を割り当てたりコピーしたりするときに害がないことを確認できましたか?

copy-ctor について: それは常に安全です (全か無か)。完了する (すべて) か、例外をスローする (何もしない) かのいずれかです。

クラスが 1 つのメンバーだけで構成されている場合 (つまり、基底クラスも存在しない場合)、代入演算子はメンバーのクラスと同じくらい安全です。複数のメンバーがある場合、代入演算子は「オール オア ナッシング」ではなくなります。2 番目のメンバーの代入演算子は例外をスローする可能性があり、その場合、オブジェクトは「途中」で代入されます。つまり、「オール オア ナッシング」割り当てを取得するには、新しいクラスにコピー アンド スワップを再度実装する必要があります。

ただし、リソースをリークしないという意味では、それでも「安全」です。もちろん、各メンバーの個別の状態は一貫しています。1 つのメンバーが割り当てられ、もう 1 つのメンバーが割り当てられていないため、新しいクラスの状態だけが一貫していません。

2 - コピー コンストラクターとスワップで引数を参照渡しすることは必須ですか?

はい、参照渡しは必須です。コピー コンストラクターはオブジェクトをコピーするものであるため、引数を値で受け取ることはできません。これは、引数をコピーする必要があることを意味するためです。これは無限再帰につながります。(引数に対して copy-ctor が呼び出されます。これは、引数に対して copy-ctor を呼び出すことを意味します。つまり、...)。swap の場合、別の理由があります。引数を値で渡す場合、swap を使用して 2 つのオブジェクトの内容を実際に交換することはできません。swap の「ターゲット」は、最初に渡されたオブジェクトのコピーになります。すぐに破壊されるもの。

3 - operator= の引数が値渡しされると、この引数に対してコピー コンストラクターが呼び出されてオブジェクトの一時コピーが生成され、このコピーが *this と交換されるというのは正しいですか? operator= で参照渡しをすると、大きな問題が発生しますよね?

はい、そうです。ただし、 const への参照によって引数を取得し、ローカル コピーを作成してから、ローカル コピーと交換することも非常に一般的です。ただし、const への参照による方法にはいくつかの欠点があります (一部の最適化が無効になります)。ただし、コピーアンドスワップを実装していない場合は、おそらく const への参照で渡す必要があります。

4 - このイディオムが Foo のコピーと割り当てにおいて完全な安全性を提供できない状況はありますか?

私が知っているものはありません。もちろん、(適切に同期されていない場合) マルチスレッドで常に失敗する可能性がありますが、それは明らかなはずです。

于 2011-05-06T00:58:09.630 に答える
2

これは、イディオムに従うとパフォーマンスが不必要に低下する (時期尚早の悲観化) という典型的な例です。これはあなたのせいではありません。コピー アンド スワップのイディオムは誇張されすぎています。いいイディオムですしかし、盲目的に従うべきではありません。

注: コンピューターで実行できる最もコストのかかる処理の 1 つは、メモリの割り当てと割り当て解除です。実際にそうするのは避ける価値があります。

注: あなたの例では、コピーアンドスワップイディオムは常に1つの割り当て解除を実行し、多くの場合(割り当てのrhsが左辺値の場合)1つの割り当ても実行します。

観察:の場合size() == rhs.size()、割り当て解除または割り当てを行う必要はありません。あなたがしなければならないのは、copy. これははるかに高速です。

Foo& operator=(const Foo& g) {
    if (size != g.size)
        Foo(g).swap(*this); 
    else
       copy(other.foo, other.foo + size, foo);
    return *this; 
}

つまり、最初にリソースをリサイクルできるケースを確認します。次に、リソースをリサイクルできない場合は、コピーアンドスワップ (またはその他) を行います。

注: 私のコメントは、他の人が与えた良い答えや正確性の問題と矛盾しません. 私のコメントは、重大なパフォーマンスの低下についてのみです。

于 2011-05-06T01:47:44.120 に答える