C++の勉強(2)
C++の勉強
Effective C++を読んで参考になったことを書いておく. とりあえず第三章まで読んだ.
コピーコンストラクタについて.
コピーコンストラクタの必要性が全くわからなかった.それはいま書いているコード特有の問題だったので,すっきりした.
サンプルコード
次の機能を持つNumericクラスを考え,main
関数での動作も同時に定める.
Numericクラスの仕様
1. int
型を保持する動的配列num
をメンバ変数とする.インスタンス化の時点でコンストラクタが呼ばれ,メンバ変数lengthが初期化される.さらに,すべての要素が0で初期化される.
2. メンバ関数Set(N)
は整数Nをnum
のすべての要素に代入する.
3. メンバ関数Print()
はnum
の0番目の要素を出力する.
4. メンバ関数Compare(N)
は,そのオブジェクトのnum
の0番目の要素と,別のオブジェクトN
のそれを比較して,同じ大きさか違う大きささかを出力する.
このクラスの実装とmain
関数で簡単な動作確認を行うためのコードが次の通り.
コピーコンストラクタの意義1
サンプルコードの動作と予想される結果
Numericクラスは一見問題なく動くように見え,その結果は以下の通りになると予想される.
num[0] = 100 num[0] = 50 ちがう大きさ
しかしながら実際に動かしてみると,上記の結果が出力されたあと,
malloc: *** error for object (ポインタが指しているアドレス): pointer being freed was not allocated
と出力されて正常に終了しない.この理由がコピーコンストラクタを定義していないことにある.
なぜうまくいかないか
オブジェクト(ここではインスタンス化されたNumericクラスn1
とn2
)を関数に渡すとき,すべてのメンバ変数が値渡しされ,その関数のスコープ内でローカルなNumericクラスのオブジェクトn
が生成される(これが暗黙に定義されるコピーコンストラクタの定義).そして,ここに落とし穴がある.メンバ変数num
はint
型のポインタだから,n
のメンバ変数num
はn2
のメンバ変数num
で初期化される.すると,その関数が終了し,スコープを外れる時点でオブジェクトn
は破棄されるので,n
のデストラクタが呼ばれる.この時,n
とn2
のnum
は同じアドレスを指していることから,n
の破棄を行うことは,n2
のnum
をdelete
することと全く同じ意味になる.したがって,その関数のスコープを外れたあとでn2
のnum
を用いて行う動作はすべて不定になる.
上で述べたエラーはn2
のnum
のdelete
がn1->Compare(n2)
で行われているのにもかかわらず,main
関数のスコープをはずれて,n2
が破棄される際にデストラクタが呼ばれたことが原因で生じているのである.
どうしたらよいか
Numericクラスにコピーコンストラクタを定義する.具体的には次の通り.
まずかった原因は,クラスオブジェクトを引数にもつ関数を呼んだ際に,ポインタ変数のコピーのされ方が一致してしまうことなので,単にコピーコンストラクタが呼ばれた際のポインタ配列のコピーのされ方を新たに決めてやればいいという話.ここでは,配列の要素ごとに代入している.この結果,ここで生じたメモリの二重解放エラーはなくなる.
まとめ
ポインタ変数をメンバー変数に持つクラスがあるなら,コピーコンストラクタを定義しておかないと思わぬエラーの原因になる.