TGViewer
Грокаем C++ Грокаем C++ @grokaemcpp · 9.36K subscribers
Post #1100 3.36K
Правило 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
More from @grokaemcpp
  1. Oct 8, 2026​​Strict weak ordering #опытным На первый взгляд, всё выглядит рабочим: мы создаём 40 зака…
  2. Oct 7, 2026​​Где-то баг... #опытным Вот вам код: struct Order { int price; int id; }; int main() { st…
  3. Oct 5, 2026Откуда spurious wakeup на кондваре? #опытным У кондваров есть метод std::condition_variabl…
  4. Oct 1, 2026​​Stacktrace. Tips #опытным Чтобы полноценно работать со стандартными трейсами, нужно знат…
  5. Sep 28, 2026​​Stacktrace #опытным Одна из проблема исключений - непонятно, откуда оно прилетело. Ну да…
  6. Sep 25, 2026​​std::spanstream #опытным Радостная весть для всех, кто пользуется iostreams! В C++23 доб…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →