明示的に既定された関数および削除された関数
C++11 では、特殊なメンバー関数を自動的に生成するかどうかを、既定化および削除指定関数によって明示的に制御できます。 また削除指定関数には、問題となる型の上位変換がすべての種類の関数 (特殊なメンバー関数、通常のメンバー関数、メンバー関数以外の関数) への引数で実行されて、不要な関数が呼び出されないようにするための、シンプルな言語が用意されています。
明示的な既定化および削除指定関数の利点
C++ では、コンパイラは型の既定のコンストラクター、コピー コンストラクター、コピー代入演算子、デストラクターを自動的に生成します (型自体で宣言されていない場合)。 これらの関数は、特殊なメンバー関数と呼ばれ、これによって C++ の単純なユーザー定義型が C の構造体のように動作します。 つまり、開発者は、追加のコード作成作業なしに、コードを作成、コピー、破棄できます。 C++11 では、移動セマンティクスが、言語に取り込まれた後、移動コンストラクターと移動代入演算子が、コンパイル時に自動生成可能な特殊なメンバー関数のリストに追加されます。
これは単純型の場合には便利ですが、複合型ではそれ自体によって特殊なメンバー関数が頻繁に定義されるために、他の特殊なメンバー関数が自動的に生成されなくなる可能性があります。 実際には、次の処理が行われています。
コンストラクターが明示的に宣言されると、既定のコンストラクターが自動的に生成されなくなります。
仮想デストラクターが明示的に宣言されると、既定のデストラクターが自動的に生成されなくなります。
移動コンストラクターまたは移動代入演算子が明示的に宣言されると、次のようになります。
コピー コンストラクターが自動的に生成されません。
コピー代入演算子が自動的に生成されません。
コピー コンストラクター、コピー代入演算子、移動コンストラクター、移動代入演算子、またはデストラクターが明示的に宣言されると、次のようになります。
移動コンストラクターが自動的に生成されません。
移動代入演算子が自動的に生成されません。
注意
さらに、C++11 標準では次の追加の規則が定義されています。
-
コピー コンストラクターまたはデストラクターが明示的に宣言されている場合、コピー代入演算子の自動生成は推奨されません。
-
コピー代入演算子またはデストラクターが明示的に宣言されている場合は、コピー コンストラクターの自動生成は推奨されません。
いずれの場合も、Visual Studio では引き続き、必要な関数が自動的かつ暗黙的に生成され、警告は出力されません。
また、これらの規則の結果がオブジェクト階層にリークすることがあります。 たとえば、何らかの理由で基底クラスの既定のコンストラクターを派生クラスから呼び出せない場合、つまり、public または protected コンストラクターがパラメーターを受け取らない場合、基底クラスの派生クラスは自身の既定のコンストラクターを自動的に生成できません。
これらの規則によって、本来単純なユーザー定義型と C++ の共通の表現形式の実装が複雑になる可能性があります。たとえば、場合によっては、コピー コンストラクターとコピー代入演算子を定義するのではなく、プライベートに宣言して、ユーザー定義型をコピー不可にするなどの作業が必要になります。
struct noncopyable
{
noncopyable() {};
private:
noncopyable(const noncopyable&);
noncopyable& operator=(const noncopyable&);
};
C++11 が登場するまで、このコード スニペットは、コピー禁止の型の慣用的な形式でした。 しかし、今では次のような複数の問題があります。
コピー コンストラクターを非表示にするためにはプライベートに宣言する必要があります。しかしどのような形でも宣言した以上、既定のコンストラクターは生成されません。 したがって、既定のコンストラクターが必要な場合は、空のコンストラクターであっても何かを明示的に定義する必要があります。
明示的に定義された既定のコンストラクターはたとえ機能しなくても、コンパイラにとっては非自明となります。 この実装方法は、既定のコンストラクターを自動的に生成するより非効率で、noncopyable が真の POD 型になる妨げになります。
コピー コンストラクターまたはコピー代入演算子が外部コードから不可視であっても、noncopyable のフレンドからは可視であり、呼び出すことができます。 コピー コンストラクターとコピー代入演算子が宣言されているが定義されていない場合、これらを呼び出すとリンカー エラーが発生します。
これは一般に許容されている表現形式ですが、特殊なメンバー関数が自動生成される規則をすべて理解していない限り、その意図が明確ではありません。
C++11 では、noncopyable の表現形式を簡単に実装できます。
struct noncopyable
{
noncopyable() =default;
noncopyable(const noncopyable&) =delete;
noncopyable& operator=(const noncopyable&) =delete;
};
以前の表現形式の問題に対する C++11 の解決策を次に説明します。
コピー コンストラクターを宣言して既定のコンストラクターの生成を防止できるのはこれまでどおりですが、明示的な既定化によりコンストラクターを既定に戻すことができます。
明示的に既定化された特殊なメンバー関数は自明として扱われるためパフォーマンスの低下がなく、noncopyable が真の POD 型になる妨げになりません。
コピー コンストラクターとコピー代入演算子は、パブリックですが削除されています。 削除指定関数を定義するか、または呼び出すと、コンパイル時エラーになります。
プログラムの意図は、=default と =delete を理解していれば明確です。 特殊なメンバー関数の自動生成の規則を理解する必要はありません。
同様に、動的にのみ割り当てられる、または動的に割り当てられない、移動禁止のユーザー定義型を作成するための表現形式も存在します。 これらの表現形式は C++11 以前の方法で実装され、同様の問題を抱えていますが、C++11 で既定化および削除指定の特殊なメンバー関数として実装することにより、同様に解決されました。
明示的な既定化関数
任意の特殊なメンバー関数を既定にすることができます。これにより、特殊なメンバー関数による既定の実装の使用を明示的に記述でき、また非パブリックのアクセス修飾子を指定して特殊なメンバー関数を定義できます。また他の状況によって自動生成が抑制されていた特殊なメンバー関数を復元できます。
特殊なメンバー関数を既定にするには、次の例のように宣言します。
struct widget
{
widget()=default;
inline widget& operator=(const widget&);
};
inline widget& widget::operator=(const widget&) =default;
インライン可能な場合は、クラス本体の外側にある特殊なメンバー関数も既定にできることに注意してください。
特殊なメンバー関数が自明であることにはパフォーマンス上のメリットがあるため、既定の動作が必要な場合は、空の関数本体を作成するよりも、特殊なメンバー関数を自動生成することを推奨します。 これには、特殊なメンバー関数を明示的に既定にする方法と、まったく宣言しない方法 (同時に他の特殊なメンバー関数も宣言せず、自動的に生成されないようにします) のいずれかを選択できます。
注意
Visual Studio では、C++11 標準で必須の移動コンストラクターまたは移動代入演算子がサポートされていません。詳細については、「C++11 の機能 (Modern C++) のサポート」の「既定化および削除指定関数」を参照してください。
削除指定関数
特殊なメンバー関数、通常のメンバー関数、および非メンバー関数を削除して、それらの関数の定義や呼び出しを防止できます。 特殊なメンバー関数を削除すると、必要のない特殊なメンバー関数がコンパイラから生成されるのを簡単に防止できます。 そのような関数が宣言されていれば削除する必要があります。ただし、宣言後に既定化されている関数は削除できません。
struct widget
{
// deleted operator new prevents widget from being dynamically allocated.
void* operator new(std::size_t) =delete;
};
通常のメンバー関数または非メンバー関数を削除すると、型の上位変換という厄介な問題が抑制され、不要な関数呼び出しを防止できます。 関数は削除された後もオーバーロードの解決に関与し、型の上位変換の後に呼び出されるよりも的確に照合できるためこの方法は効果的です。 関数呼び出しによって、より具体的な、しかし削除済みの関数に解決され、コンパイラ エラーが発生します。
// deleted overload prevents call through type promotion of float to double from succeeding.
void call_with_true_double_only(float) =delete;
void call_with_true_double_only(double param) { return; }
この例では、float 引数を使用して call_with_true_double_only を呼び出すと、コンパイラ エラーが発生しますが、int 引数を使用して call_with_true_double_only を呼び出しても、コンパイラ エラーは発生しません。int の場合、意図どおりでないとしても、引数は int から double に上位変換され、関数の double バージョンを呼び出せるようになります。 double 以外の引数を使用してこの関数を呼び出すとコンパイラ エラーが発生することを確認するには、削除された関数のテンプレート バージョンを宣言できます。
template < typename T >
void call_with_true_double_only(T) =delete; //prevent call through type promotion of any T to double from succeeding.
void call_with_true_double_only(double param) { return; } // also define for const double, double&, etc. as needed.