C++がデフォルトでスライスを許可するのが煩わしい場合もあります。例えば
struct foo { int a; };
struct bar : foo { int b; };
int main() {
bar x{1,2};
foo y = x; // <- I dont want this to compile!
}
これは 期待どおりにコンパイルおよび実行されます !しかし、スライスを有効にしたくない場合はどうなりますか?
派生クラスのインスタンスをスライスできないようにfoo
を書く慣用的な方法は何ですか?
名前付きイディオムがあるかどうかはわかりませんが、基本クラスのスライス操作よりも一致する削除済み関数をオーバーロードセットに追加できます。 foo
を
struct foo
{
int a;
foo() = default; // you have to add this because of the template constructor
template<typename T>
foo(const T&) = delete; // error trying to copy anything but a foo
template<typename T>
foo& operator=(const T&) = delete; // error assigning anything else but a foo
};
その場合は、foo
をfoo
にコピーまたはコピー割り当てすることしかできません。他のタイプでは関数テンプレートが選択され、削除された関数の使用に関するエラーが発生します。これは、あなたのクラスとそれを使用するクラスが、もはや集合体になることができないことを意味します。追加されるメンバーはテンプレートであるため、コピーコンストラクターやコピー代入演算子とは見なされないため、デフォルトのコピーおよび移動コンストラクターと代入演算子が取得されます。
2011年以降、慣用的な方法としてauto
を使用しています。
#include <iostream>
struct foo { int a; };
struct bar : foo { int b; };
int main() {
bar x{1,2};
auto y = x; // <- y is a bar
}
スライスを積極的に防ぎたい場合は、いくつかの方法があります。
特に継承が必要でない限り(多くの場合不要)、カプセル化を使用するのが通常最も好ましい方法です。
#include <iostream>
struct foo { int a; };
struct bar
{
bar(int a, int b)
: foo_(a)
, b(b)
{}
int b;
int get_a() const { return foo_.a; }
private:
foo foo_;
};
int main() {
bar x{1,2};
// foo y = x; // <- does not compile
}
もう1つのより特殊な方法は、コピーオペレーターに関する権限を変更することです。
#include <iostream>
struct foo {
int a;
protected:
foo(foo const&) = default;
foo(foo&&) = default;
foo& operator=(foo const&) = default;
foo& operator=(foo&&) = default;
};
struct bar : foo
{
bar(int a, int b)
: foo{a}, b{b}
{}
int b;
};
int main() {
auto x = bar (1,2);
// foo y = x; // <- does not compile
}
コピーコンストラクターをprotectedと宣言することで、派生クラスのメンバー関数とベース自体の外部にベースがコピーされるのを防ぐことができます。
struct foo {
// ...
protected:
foo(foo&) = default;
};