大量のデータメンバを持つ大規模なクラスLargeObjectがあるとします。
1 2 3 4 5 |
#include <cstdint> class LargeObject { std::uint64_t member_[2048]; // 大量のデータメンバ }; |
そして、LargeObjectをメンバに持つクラスがあるとします。
1 2 3 4 5 6 7 8 9 |
class ObjectHolder { public: // オブジェクトのアクセサ LargeObject& getObject() { return obj_; } private: LargeObject obj_; // 大量のデータメンバを持つクラスをメンバに持つ }; |
このObjectHolderクラスを扱うユーザーが、LargeObjectにアクセスしようとした時にちょっとしたミスを犯します。
1 2 3 4 5 6 |
int main() { ObjectHolder holder; LargeObject obj = holder.getObject(); // 参照で受けなくちゃいけないのに&書き忘れた! return 0; } |
本来ならLargeObject& で受けなくちゃいけないのに、&を書き忘れてしまいました。そしてこのコードを書いたプログラマはそのミスに気づいていません。
コンパイルエラーになることもなく、実行時に粛々とLargeObjectのコピーが発生するだけです。またアクセサ経由で受けたobjに操作しても結果はholder内に反映されません。バグの温床ですね。
これはObjectHolderを書いたプログラマも本意ではありません。
しかし想定してない操作ができてしまうことが問題です。
クラスを書くということは、どういう操作を期待しているか、想定しているかをクラス自身に語らせる必要があります。
不測のデータコピーを抑止する一つの方法としてアクセサの戻り値は参照ではなく基本的にポインタで返すというプラクティスがあります。
また別の方法としてLargeObject自身をコピー禁止にするというのも手です。
クラスのコピーコンストラクタとコピー代入演算子をアクセス不能にすることでインスタンスのコピーを禁止することができます。
1 2 3 4 5 6 |
#include <cstdint> class LargeObject { LargeObject(const LargeObject&) = delete; // c++11からは=deleteで書けるようになった LargeObject& operator=(const LargetObject) = delete; std::uint64_t member_[2048]; }; |
C++11より前の時代は=delete;による書き方が無いので、該当の関数をprivateにすることでコピー禁止を実現していました。
これで、先ほどの&忘れる事件もコンパイルエラーになり、不測のコピーが発生してもすぐに気づくことができます。
あるクラスのコピーコンストラクタ、代入演算子をアクセス不可にするとコピーは禁止できますが、第三者がそのコードを読んだ時に意図が分かりづらいという問題があります。
ですので、このコピー禁止の仕掛け部分を専用のクラスに分離した上で継承することで各クラスにコピー禁止属性を持たせるというアプローチが一般的です。
1 2 3 4 5 6 7 |
class Uncopyable { protected: // Uncopyableのインスタンスを作れないようにする為protected Uncopyable() = default; ~Uncopyable() = defualt; Uncopyable(const Uncopyable&) = delete; Uncopyable& operator=(const Uncopyable&) = delete; }; |
1 2 3 4 5 6 |
#include <cstdint> class LargeObject : Uncopyable { // コピー禁止ミックスイン private継承 std::uint64_t member_[2048]; }; |
これでLargeObjectがコピー禁止になり、さらにクラスの定義を見ただけでコピー禁止だということが分かりやすくなりました。
コピー禁止についてはEffective C++でも説明されいます。
コンパイラが生成するコピーコンストラクタとコピー代入演算子は、その基底クラスに対応する関数(同じ関数)を呼び出そうとします。しかし、対応する関数を規定クラスでprivateにしておけば、その呼び出しはエラーになるわけです。
__Effective C++ 第3版
またboost::noncopyableにも同様の実装があります。
そしてMore C++ Idioms/コピー禁止ミックスインでも紹介されています。
これらの情報も併せて参照すると良いでしょう。
まとめ
- コピー禁止するにはコピーコンストラクタにアクセス出来なくする
- 代入演算子にもアクセス出来なくする
- 既定クラスを用意することでより意図を明快に出来る