avatar
Грокаем C++
@grokaemcpp
02.07.2026 09:30
Правило 5
#новичкам

Так много разных способов определять специальные методы класса, что голова кругом. Помечать default, самому определять, доверяться компилятору, удалять, использовать обертки и тд.

Сложно, в общем.

Ну если есть что-то сложное, то люди всегда пытаются как-то его упростить и свести к набору понятных правил.

В С++ тоже есть парочка таких правил. И начнем с самого узнаваемого.

Правило 5

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

Так стоп. Почему 5, если специальных методов класса 6?

Ответ напрямую проистекает из идеи, зачем это правило вообще нужно.

Правило придумали для тех классов, которые используют нетривиальную логику управления своими ресурами.

Допустим вы зачем-то хотите хранить строку в объекте. И не как нормальные люди, а вот нетривиально, как указатель:

class PString {
std::string* ptr;
public:
PString (const std::string& str) : ptr(new std::string(str)) {}
};


Там где new, обязательно должен быть delete:

class PString {
std::string* ptr;
public:
PString (const std::string& str) : ptr(new std::string(str)) {}
~PString () {delete ptr;}
};


Так вот правило нам говорит, что раз мы определили деструктор, мы как-то необычно менеджим ресурсы в нашем классе. А если это так, то нам вряд ли подойдет, что сгенерирует компилятор. Более того, компилятор откажется сам неявно генерировать определенные методы. Поэтому нужно вручную определить все 5 специальных методов.

Ну и быстро допишем все:

class PString {
std::string* ptr_ = nullptr;
public:
PString (const std::string& str) : ptr_(new std::string(str)) {}
~PString () {delete ptr_;}
PString (const PString& other) : ptr_(new std::string(*other.ptr_)) {}
PString (PString&& other) noexcept : ptr_(other.ptr_) {ptr_ = nullptr;}
PString& operator=(const PString& other) {
if (this != &other) {
delete ptr_; // free existing resource
ptr_ = new std::string(*other.ptr_); // deep copy
}
return *this;
}
PString& operator=(PString&& x) noexcept {
if (this != &x) {
delete ptr_; // free existing resource
ptr_ = x.ptr_; // move resource and then just like in move constructor
x.ptr_ = nullptr;
}
return *this;
}
};


Мы определили деструктор, копи и мув конструкторы, а также операторы копи и мув присваивания.

Где же конструктор по-умолчанию?

1️⃣ Он не управляет ресурсами, он лишь даем им значение по умолчанию.

2️⃣ Не всегда он даже нужен, а иногда даже вреден
Что будет, если я создам кольцевой буфер по-умолчанию и попытаюсь в него запихать элемент? Будет не то же самое, что и с std::vector, не выделится новая память. В худшем случае будет запись в невыделенную память. В лучшем - будет написана проверка на дурака.
Но зачем эта проверка, когда можно просто не писать конструктор по-умолчанию и избежать ошибок?

То есть для полноценного управления ресурсами достаточно копи/мув операторов, копи/мув конструкторов и деструктора.

Follow the rules. Stay cool.

#design #goodpractice
👍 13
11
🔥 7
14 18 3K

Обсуждение 14

Обсуждение не доступно в веб-версии. Чтобы написать комментарий, перейдите в приложение Telegram.

Обсудить в Telegram

Грокаем C++

9.3K
Два сеньора C++ - Владимир и Денис - отныне ваши гиды в этом дремучем мире плюсов.

По всем вопросам (+ реклама) @ninjatelegramm

Менеджер: @Spiral_Yuri
Реклама: https://telega.in/c/grokaemcpp
Мы на TGstat: https://tgstat.ru/channel/@grokaemcpp/stat
Открыть в Telegram