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;
}
コピー割り当て演算子が正しく実装されていません。割り当てられているオブジェクトは、base
が指すオブジェクトをリークします。
デフォルトのコンストラクタも正しくありません。base
とvar
の両方が初期化されていないため、delete base;
を呼び出すときに、どちらが有効であり、デストラクタにあるかを知る方法はありません。悪いことが起こります。
コピーコンストラクターとコピー代入演算子を実装し、正しく実行したことを確認する最も簡単な方法は、 コピーアンドスワップイディオム を使用することです。
Foobar
だけが、カスタムコピーコンストラクター、代入演算子、およびデストラクターを必要とします。 Foobase
は、コンパイラーが提供するデフォルトの動作で十分なので、1つは必要ありません。
Foobar
の場合、代入演算子にリークがあります。割り当てる前にオブジェクトを解放することで簡単に修正でき、それで十分です。しかし、_secondポインターメンバーをFoobar
に追加した場合、状況が複雑になることがわかります。ここで、2番目のポインターの割り当て中に例外が発生した場合、破損またはリークを回避するために、最初に割り当てたポインターを適切にクリーンアップする必要があります。また、ポインターメンバーを追加すると、多項式の方法よりも複雑になります。
代わりに、コピーコンストラクターの観点から代入演算子を実装します。次に、スローしないスワップ関数の観点から、コピーコンストラクタを実装する必要があります。詳細については、コピーとスワップのイディオムについてお読みください。
また、Foobar
のデフォルトのコンストラクターは、メンバーをデフォルトで初期化しません。これは悪いことです。ユーザーが期待するものではないからです。メンバーポインターは任意のアドレスを指し、intは任意の値を持ちます。これで、コンストラクターが作成したオブジェクトを使用すると、未定義の動作ランドに非常に近くなります。
私はあなたのために非常に単純なパッチを持っています:
class Foobar
{
int var;
std::unique_ptr<FooBase> base;
...
それはあなたを始めるはずです。
一番下の行は:
delete
を呼び出さないでください(専門家はポイント2を参照)delete
を呼び出さないでください(ご存知でしょう...)