web-dev-qa-db-ja.com

スライスを防ぐ慣用的な方法は?

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を書く慣用的な方法は何ですか?

17

名前付きイディオムがあるかどうかはわかりませんが、基本クラスのスライス操作よりも一致する削除済み関数をオーバーロードセットに追加できます。 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
};

その場合は、foofooにコピーまたはコピー割り当てすることしかできません。他のタイプでは関数テンプレートが選択され、削除された関数の使用に関するエラーが発生します。これは、あなたのクラスとそれを使用するクラスが、もはや集合体になることができないことを意味します。追加されるメンバーはテンプレートであるため、コピーコンストラクターやコピー代入演算子とは見なされないため、デフォルトのコピーおよび移動コンストラクターと代入演算子が取得されます。

20
NathanOliver

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
}
5
Richard Hodges

コピーコンストラクターをprotectedと宣言することで、派生クラスのメンバー関数とベース自体の外部にベースがコピーされるのを防ぐことができます。

struct foo {
    // ...
protected:
    foo(foo&) = default;
};
3
eerorika