Идиоматический способ предотвращения нарезки?

Иногда это может раздражать, что 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, что нельзя нарезать экземпляры любого производного класса?

Ответ 1

Я не уверен, существует ли для него именованная идиома, но вы можете добавить удаленную функцию к набору перегрузки, которая лучше соответствует операциям среза базовых классов. Если вы измените 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. Любой другой тип выберет шаблон функции, и вы получите ошибку об использовании удаленной функции. Это означает, что ваш класс и использующие его классы больше не могут быть агрегатными. Поскольку добавляемые элементы являются шаблонами, они не рассматриваются как конструкторы копирования или операторы копирования, поэтому вы получите конструкторы копирования и перемещения по умолчанию и операторы назначения.

Ответ 2

С 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

}

Другим более специализированным способом может быть изменение разрешений для операторов копирования:

#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
}

Ответ 3

Вы можете предотвратить копирование базы за пределы функций-членов производных классов и самой базы, объявив конструктор копирования защищенным:

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