14

Consider a class of which copies need to be made. The vast majority of the data elements in the copy must strictly reflect the original, however there are select few elements whose state is not to be preserved and need to be reinitialized.

Is it bad form to call a default assignment operator from the copy constructor?

The default assignment operator will behave well with Plain Old Data( int,double,char,short) as well user defined classes per their assignment operators. Pointers would need to be treated separately.

One drawback is that this method renders the assignment operator crippled since the extra reinitialization is not performed. It is also not possible to disable the use of the assignment operator thus opening up the option of the user to create a broken class by using the incomplete default assignment operator A obj1,obj2; obj2=obj1; /* Could result is an incorrectly initialized obj2 */ .

It would be good to relax the requirement that to a(orig.a),b(orig.b)... in addition to a(0),b(0) ... must be written. Needing to write all of the initialization twice creates two places for errors and if new variables (say double x,y,z) were to be added to the class, initialization code would need to correctly added in at least 2 places instead of 1.

Is there a better way?

Is there be a better way in C++0x?

class A {
  public:
    A(): a(0),b(0),c(0),d(0)
    A(const A & orig){
      *this = orig;       /* <----- is this "bad"? */
      c = int();
    }
  public:
    int a,b,c,d;
};

A X;
X.a = 123;
X.b = 456;
X.c = 789;
X.d = 987;

A Y(X);

printf("X: %d %d %d %d\n",X.a,X.b,X.c,X.d);
printf("Y: %d %d %d %d\n",Y.a,Y.b,Y.c,Y.d);

Output:

X: 123 456 789 987
Y: 123 456 0 987

Alternative Copy Constructor:

A(const A & orig):a(orig.a),b(orig.b),c(0),d(orig.d){}  /* <-- is this "better"? */
4

6 に答える 6

15

broneが指摘しているように、コピーの作成という観点から割り当てを実装する方がよいでしょう。私は彼の代わりのイディオムを好みます:

T& T::operator=(T t) {
    swap(*this, t);
    return *this;
}

少し短く、難解言語機能を利用してパフォーマンスを向上させることができます。他の優れたC++コードと同様に、注意すべき微妙な点もいくつかあります。

まず、tパラメーターが意図的に値によって渡されるため、コピーコンストラクターが呼び出され(ほとんどの場合)、元の値に影響を与えることなく、心ゆくまで変更できます。を使用const T&するとコンパイルに失敗しT&、assigned-from値を変更することで驚くべき動作を引き起こします。

この手法swapでは、型の代入演算子を使用しない方法で型に特化する必要もあります(そうしstd::swapないと、無限の再帰が発生します)。に特化していない場合、範囲内に引き込まれ、問題が発生するため、迷子using std::swapやが発生しないように注意してください。オーバーロード解決とADLは、定義した場合、正しいバージョンのスワップが使用されることを保証します。using namespace stdstd::swapswapT

swapタイプを定義する方法はいくつかあります。最初のメソッドは、swapメンバー関数を使用して実際の作業を行い、次のswapようにそれに委任する特殊化を備えています。

class T {
public:
    // ....
    void swap(T&) { ... }
};

void swap(T& a, T& b) { a.swap(b); }

これは標準ライブラリではかなり一般的です。std::vectorたとえば、この方法でスワッピングが実装されています。メンバー関数がある場合はswap、代入演算子から直接呼び出すだけで、関数ルックアップの問題を回避できます。

もう1つの方法はswap、フレンド関数として宣言し、すべての作業を実行させることです。

class T {
    // ....
    friend void swap(T& a, T& b);
};

void swap(T& a, T& b) { ... }

swap()通常はクラスのインターフェースの不可欠な部分ではないので、私は2番目のものを好みます。無料の関数としてより適切なようです。しかし、それは好みの問題です。

型に最適化swapすることは、C ++ 0xで右辺値参照の利点のいくつかを達成するための一般的な方法です。したがって、クラスがそれを利用でき、本当にパフォーマンスが必要な場合は、一般的に良い考えです。

于 2009-10-07T19:59:11.403 に答える
4

お使いのバージョンのコピー コンストラクターでは、メンバーは最初に既定で構築され、次に割り当てられます。
整数型ではこれは問題ではありませんが、std::strings のような自明でないメンバーがある場合、これは不必要なオーバーヘッドになります。

したがって、はい、一般的に代替コピーコンストラクターの方が優れていますが、メンバーとして整数型しかない場合は、実際には問題になりません。

于 2009-10-07T19:50:22.797 に答える
2

基本的に、あなたが言っているのは、クラスのアイデンティティに貢献しないクラスのメンバーがいるということです。現状では、代入演算子を使用してクラスメンバーをコピーし、コピーしてはならないメンバーをリセットすることでこれを表現しています。これにより、コピー コンストラクターと矛盾する代入演算子が残ります。

コピー アンド スワップ イディオムを使用し、コピー コンストラクターでコピーしてはならないメンバーを表現する方がはるかに優れています。「このメンバーをコピーしない」動作が表現されている場所がまだ 1 つありますが、代入演算子とコピー コンストラクターは一貫しています。

class A
{
public:

    A() : a(), b(), c(), d() {}

    A(const A& other)
        : a(other.a)
        , b(other.b)
        , c() // c isn't copied!
        , d(other.d)

    A& operator=(const A& other)
    {
        A tmp(other); // doesn't copy other.c
        swap(tmp);
        return *this;
    }

    void Swap(A& other)
    {
        using std::swap;
        swap(a, other.a);
        swap(b, other.b);
        swap(c, other.c); // see note
        swap(d, other.d);
    }

private:
    // ...
};

注:メンバー関数では、メンバーswapを交換しましたcc代入演算子で使用するために、これはコピー コンストラクターの動作と一致するように動作を保持します。つまり、メンバーを再初期化します。関数をパブリックのままにするswapか、無料の関数を介してアクセスを提供するswap場合は、この動作がスワップの他の用途に適していることを確認する必要があります。

于 2009-10-08T06:37:16.087 に答える
1

個人的には壊れた代入演算子がキラーだと思います。私はいつも、人々はドキュメントを読み、それが禁止されていることは何もすべきではないと言っていますが、それでも、何も考えずに代入を書いたり、型が代入可能である必要があるテンプレートを使用したりするのは簡単すぎます。コピー不可能なイディオムには理由があります。ifoperator=がうまくいかない場合、アクセス可能にしておくのは危険すぎるからです。

私の記憶が正しければ、C++0x では次のことが可能になります。

private:
    A &operator=(const A &) = default;

少なくとも、壊れたデフォルト代入演算子を使用できるのはクラス自体だけであり、この制限されたコンテキストでは注意が容易になることを願っています。

于 2009-10-07T21:47:32.657 に答える
0

動作が些細な場合は、コピーコンストラクターを実装しない方が良いと思います(あなたの場合、壊れているようです:少なくとも割り当てとコピーは同様のセマンティクスを持つ必要がありますが、コードはそうではないことを示唆しています-しかし、私はこれは不自然な例だと思います)。あなたのために生成されたコードが間違っているはずがありません。

これらのメソッドを実装する必要がある場合、ほとんどの場合、クラスは高速スワップ メソッドを使用できるため、コピー コンストラクターを再利用して代入演算子を実装できます。

何らかの理由でデフォルトの浅いコピー コンストラクターを提供する必要がある場合、C++0X には

 X(const X&) = default;

しかし、奇妙なセマンティクスのイディオムはないと思います。この場合、初期化の代わりに代入を使用すると安価です (int を初期化せずに残してもコストがかからないため)。

于 2009-10-07T23:43:44.097 に答える
0

すべてのオブジェクトを二重に割り当てるからではなく、私の経験では、特定の機能セットに対してデフォルトのコピーコンストラクター/代入演算子に依存するのは悪い形式であることが多いため、これを悪い形式と呼びます。これらはソースのどこにもないため、必要な動作がそれらの動作に依存するかどうかを判断するのは困難です。たとえば、年内の誰かが文字列のベクトルをクラスに追加したい場合はどうなるでしょうか? 単純な古いデータ型はもうありません。メンテナーがそれらが何かを壊していることを知るのは非常に困難です。

DRY は良いことですが、微妙な未指定の要件を作成することは、メンテナンスの観点からははるかに悪いと思います。自分自身を繰り返しても、それは悪いことですが、それほど悪いことではありません。

于 2009-10-07T20:54:22.477 に答える