6

私は最近、ここで見られるコピーコンストラクター、代入演算子、コピースワップイディオムを再検討しました: コピーアンドスワップイディオムとは何ですか? と他の多くの場所-

上記のリンクは素晴らしい投稿です-しかし、私はまだいくつかの質問がありました-これらの質問は、stackoverflowや他の多くのサイトで、たくさんの場所で答えられていますが、私は多くの一貫性を見ていません-

1-コピーコンストラクターでディープコピー用に新しいメモリを割り当てる領域の周りにtry-ある必要がありますか?catch(私はそれを両方の方法で見ました)

2-コピーコンストラクタと代入演算子の両方の継承に関して、基本クラス関数をいつ呼び出す必要があり、これらの関数をいつ仮想にする必要がありますか?

3- std::copyコピーコンストラクタでメモリを複製するための最良の方法はありますか?私はそれをで見ました、そして他の人が地球上で最悪のことをmemcpy言うのを見ました。memcpy


以下の例を考えてみてください(すべてのフィードバックに感謝します)、それはいくつかの追加の質問を促しました:

4-自己割り当てをチェックする必要がありますか?もしそうならどこ

5-トピック外の質問ですが 、swapが「FirsttoLast」から0番目の要素がOther.Dataである場合、次のようstd::copy(Other.Data,Other.Data + size,Data); に 使用されるのを見てきました。std::copy(Other.Data,Other.Data + (size-1),Data);

6-コメントアウトされたコンストラクターが機能しないのはなぜですか(サイズをmysizeに変更する必要がありました)-これは、作成する順序に関係なく、コンストラクターが常に最初に割り当て要素を呼び出すことを意味すると想定されますか?

7-私の実装に関する他のコメントはありますか?コードが役に立たないことは知っていますが、要点を説明しようとしています。

class TBar
{

    public:

    //Swap Function        
    void swap(TBar &One, TBar &Two)
    {
            std::swap(One.b,Two.b);
            std::swap(One.a,Two.a);
    }

    int a;
    int *b;


    TBar& operator=(TBar Other)
    {
            swap(Other,*this);
            return (*this);
    }

    TBar() : a(0), b(new int) {}                //We Always Allocate the int 

    TBar(TBar const &Other) : a(Other.a), b(new int) 
    {
            std::copy(Other.b,Other.b,b);
            *b = 22;                                                //Just to have something
    }

    virtual ~TBar() { delete b;}
};

class TSuperFoo : public TBar
{
    public:

    int* Data;
    int size;

    //Swap Function for copy swap 
    void swap (TSuperFoo &One, TSuperFoo &Two)
    {
            std::swap(static_cast<TBar&>(One),static_cast<TBar&>(Two));
            std::swap(One.Data,Two.Data);
            std::swap(One.size,Two.size);
    }

    //Default Constructor
    TSuperFoo(int mysize = 5) : TBar(), size(mysize), Data(new int[mysize]) {}
    //TSuperFoo(int mysize = 5) : TBar(), size(mysize), Data(new int[size]) {}                *1

    //Copy Constructor
    TSuperFoo(TSuperFoo const &Other) : TBar(Other), size(Other.size), Data(new int[Other.size])        // I need [Other.size]! not sizw
    {
            std::copy(Other.Data,Other.Data + size,Data);        // Should this be (size-1) if std::copy is First -> Last? *2
    }

    //Assignment Operator
    TSuperFoo& operator=(TSuperFoo Other)
    {
            swap(Other,(*this));
            return (*this);
    }

    ~TSuperFoo() { delete[] Data;}

};
4

4 に答える 4

4
  1. メモリを割り当てる場合は、例外がスローされた場合にメモリが解放されていることを確認する必要があります。これは、明示的なtry/を使用して行うことも、メモリを保持するcatchなどのスマートポインターを使用することもできます。このstd::unique_ptrポインターは、スタックの巻き戻しによってスマートポインターが破棄されると、自動的に削除されます。

  2. virtual代入演算子が必要になることはめったにありません。メンバー初期化リストで基本クラスのコピーコンストラクターを呼び出します。メンバーごとの代入を行う場合は、派生代入演算子で最初に基本クラスの代入演算子を呼び出します。コピー/スワップを行う場合は、を呼び出す必要はありません。コピーとスワップが正しく実装されている場合は、派生代入演算子での基本クラスの代入。

  3. std::copyオブジェクトを操作し、コピーコンストラクターを正しく呼び出します。プレーンなPODオブジェクトがある場合はmemcpy、同様に機能します。ただし、ほとんどの場合は、PODの内部でstd::copy最適化する必要があり、後でコピーコンストラクターを追加した場合にエラーが発生する可能性を回避できます。memcpy

[更新された質問の更新]

  1. 書かれているようにコピー/スワップを使用すると、自己代入をチェックする必要はなく、実際にそうする方法はありません---代入演算子を入力するまでにotherコピーあり、ソースオブジェクトが何であるかを知る方法がありませんだった。これは、自己割り当てが引き続きコピー/スワップを実行することを意味します。

  2. std::copy入力としてイテレータのペア(first、first + size)を取ります。これにより、空の範囲が可能になり、標準ライブラリのすべての範囲ベースのアルゴリズムと同じになります。

  3. メンバー初期化子リストの順序に関係なく、メンバーは宣言された順序で初期化されるため、コメント化されたコンストラクターは機能しません。したがって、Data常に最初に初期化されます。初期化がに依存している場合、まだ初期化されていないsizeため、duff値を取得します。sizeの宣言を交換するsizedata、このコンストラクターは正常に機能します。優れたコンパイラーは、メンバーの初期化の順序が宣言の順序と一致しないことについて警告します。

于 2012-06-26T14:04:43.137 に答える
3

1-コピーコンストラクターのディープコピーに新しいメモリを割り当てる領域を試してみる必要がありますか?

一般に、例外を処理できる場合にのみ例外をキャッチする必要があります。ローカルでメモリ不足状態に対処する方法がある場合は、それをキャッチします。そうでなければ、それを手放します。

構築が失敗した場合、コンストラクターから正常に戻るべきではありません。これにより、呼び出し元に無効なオブジェクトが残り、それが無効であることを知る方法がなくなります。

2-コピーコンストラクタと代入演算子の両方の継承に関して、基本クラス関数をいつ呼び出す必要があり、これらの関数をいつ仮想にする必要がありますか?

仮想関数はオブジェクトによってのみディスパッチでき、作成する前にオブジェクトがないため、コンストラクターを仮想にすることはできません。通常、代入演算子も仮想化しません。コピー可能で割り当て可能なクラスは、通常、非多形の「値」型として扱われます。

通常、初期化子リストから基本クラスのコピーコンストラクターを呼び出します。

Derived(Derived const & other) : Base(other), <derived members> {}

コピーアンドスワップのイディオムを使用している場合、代入演算子は基本クラスについて心配する必要はありません。それはスワップによって処理されます:

void swap(Derived & a, Derived & b) {
    using namespace std;
    swap(static_cast<Base&>(a), static_cast<Base&>(b));
    // and swap the derived class members too
}
Derived & Derived::operator=(Derived other) {
    swap(*this, other);
    return *this;
}

3-std::copyコピーコンストラクタでメモリを複製するための最良の方法はありますか?私はそれをで見ました、そして他の人が地球上で最悪のことをmemcopy言うのを見ました。memcopy

生の記憶を扱うのはかなり珍しいことです。通常、クラスにはオブジェクトが含まれており、多くの場合、オブジェクトを単にメモリをコピーするだけでは正しくコピーできません。コピーコンストラクターまたは代入演算子を使用してオブジェクトをコピーしstd::copy、代入演算子を使用してオブジェクトの配列(またはより一般的にはオブジェクトのシーケンス)をコピーします。

本当に必要な場合は、POD(プレーンな古いデータ)オブジェクトと配列memcpyをコピーするために使用できます。ただし、エラーが発生しにくく(オブジェクトサイズを指定する必要がないため)、脆弱性が低く(オブジェクトを非PODに変更しても壊れないため)、潜在的に高速です(オブジェクトサイズと配置が異なるため)。コンパイル時に認識されます)。std::copy

于 2012-06-26T14:09:32.407 に答える
1
  1. ディープコピーしているもののコンストラクターが処理できるものをスローする可能性がある場合は、先に進んでそれをキャッチします。ただし、メモリ割り当ての例外を伝播させるだけです。
  2. コピーコンストラクター(または任意のコンストラクター)を仮想にすることはできません。これらの基本クラス初期化子を含めます。コピー代入演算子は、仮想であっても基本クラスに委任する必要があります。
  3. memcpy()C ++でクラス型をコピーするには低レベルであり、未定義の動作につながる可能性があります。私std::copyは通常、より良い選択だと思います。
于 2012-06-26T14:01:48.690 に答える
1
  1. try-catch何かを元に戻す必要があるときに使用できます。それ以外の場合はbad_alloc、呼び出し元に伝播させます。

  2. 基本クラスのコピーコンストラクターまたは代入演算子を呼び出すことは、そのコピーを処理するための標準的な方法です。仮想代入演算子のユースケースを見たことがないので、まれだと思います。

  3. std::copyクラスオブジェクトを正しくコピーするという利点があります。memcpy処理できるタイプにはかなり制限があります。

于 2012-06-26T14:05:40.200 に答える