Ответ 1
У меня есть существенные оговорки в отношении этого руководства. Даже зная, что это руководство, а не правило, у меня все еще есть оговорки.
Скажем, у вас есть пользовательский класс, похожий на 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
.