コピー禁止Mix-in

大量のデータメンバを持つ大規模なクラスLargeObjectがあるとします。

そして、LargeObjectをメンバに持つクラスがあるとします。

このObjectHolderクラスを扱うユーザーが、LargeObjectにアクセスしようとした時にちょっとしたミスを犯します。

本来ならLargeObject& で受けなくちゃいけないのに、&を書き忘れてしまいました。そしてこのコードを書いたプログラマはそのミスに気づいていません。

コンパイルエラーになることもなく、実行時に粛々とLargeObjectのコピーが発生するだけです。またアクセサ経由で受けたobjに操作しても結果はholder内に反映されません。バグの温床ですね。

これはObjectHolderを書いたプログラマも本意ではありません。

しかし想定してない操作ができてしまうことが問題です。

クラスを書くということは、どういう操作を期待しているか、想定しているかをクラス自身に語らせる必要があります。

不測のデータコピーを抑止する一つの方法としてアクセサの戻り値は参照ではなく基本的にポインタで返すというプラクティスがあります。

また別の方法としてLargeObject自身をコピー禁止にするというのも手です。

クラスのコピーコンストラクタとコピー代入演算子をアクセス不能にすることでインスタンスのコピーを禁止することができます。

C++11より前の時代は=delete;による書き方が無いので、該当の関数をprivateにすることでコピー禁止を実現していました。

これで、先ほどの&忘れる事件もコンパイルエラーになり、不測のコピーが発生してもすぐに気づくことができます。

あるクラスのコピーコンストラクタ、代入演算子をアクセス不可にするとコピーは禁止できますが、第三者がそのコードを読んだ時に意図が分かりづらいという問題があります。

ですので、このコピー禁止の仕掛け部分を専用のクラスに分離した上で継承することで各クラスにコピー禁止属性を持たせるというアプローチが一般的です。

これでLargeObjectがコピー禁止になり、さらにクラスの定義を見ただけでコピー禁止だということが分かりやすくなりました。

コピー禁止についてはEffective C++でも説明されいます。

コンパイラが生成するコピーコンストラクタとコピー代入演算子は、その基底クラスに対応する関数(同じ関数)を呼び出そうとします。しかし、対応する関数を規定クラスでprivateにしておけば、その呼び出しはエラーになるわけです。

__Effective C++ 第3版

 

またboost::noncopyableにも同様の実装があります。

そしてMore C++ Idioms/コピー禁止ミックスインでも紹介されています。

これらの情報も併せて参照すると良いでしょう。

まとめ

  1. コピー禁止するにはコピーコンストラクタにアクセス出来なくする
  2. 代入演算子にもアクセス出来なくする
  3. 既定クラスを用意することでより意図を明快に出来る
Pocket