型によっては、コピーという概念が存在しないものがある。
例えばコピー不可能なシステムのリソースを扱うクラスだ。
具体的にはファイル、スレッド、プロセス、ネットワークソケットといったリソースだ。このようなリソースを管理するクラスを作ったとして、いったいコピーをどうすればいいのだろうか。
コピーできないクラスはdeleted定義
を使ってコピーコンストラクターとコピー代入演算子を消すことができる。
deleted定義
は関数の本体{...}
の代わりに= delete
を書く。deleted定義
されている関数を使うとエラーとなる。
struct X
{
// コピーコンストラクター
X( const X & ) = delete ;
// コピー代入演算子
X & operator = ( const X & ) = delete ;
// デフォルトコンストラクター
X() { }
// ムーブコンストラクター
X ( X && ) { }
// ムーブ代入演算子
X & operator = ( X && ) { }
} ;
このようなクラスX
は、コピーできない。
int main()
{
// デフォルト構築できる
X a ;
// エラー、コピーできない
X b = a ;
b = a ;
// OK、ムーブはできる。
X c = std::move(a) ;
}
クラスX
はコピーコンスラクターとコピー代入演算子がdeleted定義
されているために、コピーをすることができない。
コピーやムーブが禁止されている型をデータメンバーに持つクラスは、デフォルトのコピーやムーブができなくなる。
// コピーできない型
struct Uncopyable
{
Uncopyable(){}
Uncopyable( const Uncopyable & ) = delete ;
Uncopyable & operator = ( const Uncopyable & ) = delete ;
} ;
// デフォルトのコピーができない
struct X
{
Uncopyable member ;
} ;
deleted定義
を使えばムーブも禁止できる。ただし、ムーブを禁止するというのは現実的にはあまり実用性がない。というのも、コピーはムーブでもあるので、コピーを提供している型はムーブとしてコピーを行えばムーブも提供できることになる。
C++には「5原則」という作法がある。
5原則とは、
- コピーコンストラクター
- コピー代入演算子
- ムーブコンストラクター
- ムーブ代入演算子
- デストラクター
このうちの1つを独自に定義したならば、残りの4つも定義すべきである。
というものだ。
なぜか。コピーやムーブを独自に定義するということは、デフォルトのコピーやムーブでは足りない何らかの処理をしたいはずだ。その処理には、たいていの場合何らかの破棄の処理が必要で、するとデストラクターも定義しなければならない。
同様に、デストラクターで何らかの独自の処理をするということは、コピーやムーブでも何らかの処理をしたいはずだ。