Правильно ли CppCoreGuidelines C.21?

Читая CoreCppGuidelines Бьярна Страуструпа, я нашел руководство, которое противоречит моему опыту.

С.21 требуется следующее:

Если вы определите или =delete любая операция по умолчанию, определить или =delete торговый центр

По следующей причине:

Семантика специальных функций тесно связана, поэтому, если нужно быть нестандартным, вероятнее всего, что другие тоже нуждаются в модификации.

По моему опыту, две наиболее распространенные ситуации переопределения операций по умолчанию следующие:

# 1: Определение виртуального деструктора с телом по умолчанию для разрешения наследования:

class C1
{
...
virtual ~C1() = default;
}

# 2: Определение конструктора по умолчанию, выполняющего некоторую инициализацию членов RAII-типа:

class C2
{
public:
int a; float b; std::string c; std::unique_ptr<int> x;

C2() : a(0), b(1), c("2"), x(std::make_unique<int>(5))
{}
}

Все другие ситуации были редкими в моем опыте.

Что вы думаете об этих примерах? Являются ли они исключениями из правила C.21 или здесь лучше определить все операции по умолчанию? Есть ли другие частые исключения?

12

Решение

У меня есть значительные оговорки с этим руководством. Даже зная, что это директива, и не правило, я еще есть оговорки.

Допустим, у вас есть пользовательский класс, похожий на std::complex<double>, или же std::chrono::seconds, Это просто тип значения. Он не владеет никакими ресурсами, он должен быть простым. Допустим, у него есть конструктор без специальных членов.

class SimpleValue
{
int value_;
public:
explicit SimpleValue(int value);
};

Ну я тоже хочу SimpleValue быть конструируемым по умолчанию, и я запретил конструктор по умолчанию, предоставив другой конструктор, поэтому мне нужно добавить, что специальный член:

class SimpleValue
{
int value_;
public:
SimpleValue();
explicit SimpleValue(int value);
};

Я боюсь, что люди запомнят это руководство и причину: ну, поскольку я предоставил одного специального члена, я должен определить или удалить остальных, так что здесь …

class SimpleValue
{
int value_;
public:
~SimpleValue() = default;
SimpleValue();
SimpleValue(const SimpleValue&) = default;
SimpleValue& operator=(const SimpleValue&) = default;

explicit SimpleValue(int value);
};

Хм … Мне не нужны члены движения, но мне нужно бездумно следовать тому, что сказали мне мудрые, так что я просто удалю их:

class SimpleValue
{
int value_;
public:
~SimpleValue() = default;
SimpleValue();
SimpleValue(const SimpleValue&) = default;
SimpleValue& operator=(const SimpleValue&) = default;
SimpleValue(SimpleValue&&) = delete;
SimpleValue& operator=(SimpleValue&&) = delete;

explicit SimpleValue(int value);
};

Я боюсь, что CoreCppGuidelines C.21 приведет к тому, что тонна кода будет выглядеть именно так. Почему это плохо? Пара причин:

1. Это гораздо сложнее для чтения, чем эта правильная версия:

class SimpleValue
{
int value_;
public:
SimpleValue();
explicit SimpleValue(int value);
};

2. это сломанный. Вы узнаете, когда в первый раз попытаетесь вернуть SimpleValue из функции по значению:

SimpleValue
make_SimpleValue(int i)
{
// do some computations with i
SimpleValue x{i};
// do some more computations
return x;
}

Это не скомпилируется. Сообщение об ошибке скажет что-то о доступе к удаленному члену SimpleValue,

У меня есть несколько лучших рекомендаций:

1. Знайте, когда компилятор по умолчанию или удаляет специальные члены для вас, и что будут делать члены по умолчанию.

Этот график может помочь с этим:

введите описание изображения здесь

Если этот график далеко Слишком сложно, я понимаю. Это является сложный. Но когда это объясняется вам чуть-чуть за раз, гораздо легче иметь дело. я буду с надеждой обновлять этот ответ в течение недели со ссылкой на видео со мной, объясняющим этот график. Вот ссылка на объяснение, после более долгой задержки, чем мне бы хотелось (мои извинения): https://www.youtube.com/watch?v=vLinb2fgkHk

2. Всегда определяйте или удаляйте специальный член, если неявное действие компилятора не является правильным.

3. Не зависит от устаревшего поведения (красные рамки на графике выше). Если вы объявляете любой из деструктора, конструктора копирования или оператора назначения копирования, тогда объявляете как конструктор копирования, так и оператор назначения копирования.

4. Никогда удалить участников перемещения. Если вы это сделаете, в лучшем случае это будет излишним. В худшем случае это сломает ваш класс (как в SimpleValue пример выше). Если вы действительно удаляете элементы перемещения, и это избыточный случай, вы заставляете своих читателей постоянно просматривать ваш класс, чтобы убедиться, что это не поврежденный случай.

5. Примите нежные любовные заботы к каждому из 6 специальных членов, даже если в результате компилятор разрешит вам это (возможно, запретив или удалив их неявно).

6. Поместите своих специальных членов в последовательном порядке в верхней части вашего класса (только те, которые вы хотите объявить явно), чтобы ваши читатели не должны были искать их. У меня есть мой любимый заказ, если ваш предпочтительный заказ отличается, хорошо. Мой предпочтительный заказ — это то, что я использовал в SimpleValue пример.

7

Другие решения

Я думаю, что ваш второй пример — разумное исключение, и, в конце концов, руководство говорит: «шансы есть …», поэтому будут некоторые исключения.

Мне интересно, может ли этот слайд помочь с вашим первым примером:

Специальные члены

Вот слайды: https://accu.org/content/conf2014/Howard_Hinnant_Accu_2014.pdf

РЕДАКТИРОВАТЬ: Для получения дополнительной информации о первом случае, я с тех пор обнаружил это: C ++ 11 виртуальных деструкторов и автоматическая генерация специальных функций перемещения

6

По вопросам рекламы [email protected]