web-dev-qa-db-ja.com

C ++:コピーコンストラクターとコピー代入演算子の実装

C++でのコピーコンストラクターとコピー代入演算子について読んだ後、簡単な例を作成してみました。以下のスニペットは明らかに機能しますが、コピーコンストラクターとコピー代入演算子を正しく実装しているかどうかはわかりません。関連する概念を理解するための間違いや改善点、またはより良い例があるかどうかを教えてください。

class Foobase
{
    int bInt;

public:
    Foobase() {}

    Foobase(int b) { bInt = b;}

    int GetValue() { return bInt;}

    int SetValue(const int& val) { bInt = val; }
};


class Foobar
{
    int var;    
    Foobase *base;      

public:
    Foobar(){}

    Foobar(int v)
    {
        var = v;        
        base = new Foobase(v * -1);

    }

    //Copy constructor
    Foobar(const Foobar& foo)
    {       
        var = foo.var;
        base = new Foobase(foo.GetBaseValue());
    }

    //Copy assignemnt operator
    Foobar& operator= (const Foobar& other)
    {
        if (this != &other) // prevent self-assignment
        {
            var = other.var;
            base = new Foobase(other.GetBaseValue());

        }
        return *this;
    }

    ~Foobar()
    {
        delete base;
    }

    void SetValue(int val)
    {
        var = val;
    }

    void SetBaseValue(const int& val)
    {
        base->SetValue(val);
    }

    int GetBaseValue() const
    {
        return(base->GetValue());
    }

    void Print()
    {
        cout<<"Foobar Value: "<<var<<endl;
        cout<<"Foobase Value: "<<base->GetValue()<<endl;

    }   

};

int main()
{
    Foobar f(10);       
    Foobar g(f);  //calls copy constructor
    Foobar h = f; //calls copy constructor

    Foobar i;
    i = f;

    f.SetBaseValue(12);
    f.SetValue(2);    

    Foobar j = f = z; //copy constructor for j but assignment operator for f

    z.SetBaseValue(777);
    z.SetValue(77);

    return 1;
}
18
blitzkriegz

コピー割り当て演算子が正しく実装されていません。割り当てられているオブジェクトは、baseが指すオブジェクトをリークします。

デフォルトのコンストラクタも正しくありません。basevarの両方が初期化されていないため、delete base;を呼び出すときに、どちらが有効であり、デストラクタにあるかを知る方法はありません。悪いことが起こります。

コピーコンストラクターとコピー代入演算子を実装し、正しく実行したことを確認する最も簡単な方法は、 コピーアンドスワップイディオム を使用することです。

15
James McNellis

Foobarだけが、カスタムコピーコンストラクター、代入演算子、およびデストラクターを必要とします。 Foobaseは、コンパイラーが提供するデフォルトの動作で十分なので、1つは必要ありません。

Foobarの場合、代入演算子にリークがあります。割り当てる前にオブジェクトを解放することで簡単に修正でき、それで十分です。しかし、_secondポインターメンバーをFoobarに追加した場合、状況が複雑になることがわかります。ここで、2番目のポインターの割り当て中に例外が発生した場合、破損またはリークを回避するために、最初に割り当てたポインターを適切にクリーンアップする必要があります。また、ポインターメンバーを追加すると、多項式の方法よりも複雑になります。

代わりに、コピーコンストラクターの観点から代入演算子を実装します。次に、スローしないスワップ関数の観点から、コピーコンストラクタを実装する必要があります。詳細については、コピーとスワップのイディオムについてお読みください。

また、Foobarのデフォルトのコンストラクターは、メンバーをデフォルトで初期化しません。これは悪いことです。ユーザーが期待するものではないからです。メンバーポインターは任意のアドレスを指し、intは任意の値を持ちます。これで、コンストラクターが作成したオブジェクトを使用すると、未定義の動作ランドに非常に近くなります。

2
wilhelmtell

私はあなたのために非常に単純なパッチを持っています:

class Foobar
{
  int var;    
  std::unique_ptr<FooBase> base;

...

それはあなたを始めるはずです。

一番下の行は:

  1. コードでdeleteを呼び出さないでください(専門家はポイント2を参照)
  2. コードでdeleteを呼び出さないでください(ご存知でしょう...)
1
Matthieu M.