2. Керування пам'яттю в C++
Ручне керування пам'яттю
Отримання ресурсу є ініціалізація ==
Resource acquisition is initialization == RAII
3. RAII
Кожен ресурс обгортається в клас, в якому:
конструктор отримує ресурс і встановлює всі
інваріанти класу, або кидає виключення, якщо це
не вдається зробити
деструктор звільняє ресурс і не кидає виключень
Ресурс завжди зберігається в об'єкті RAII-
класу, який створений на стеку чи
тимчасовий, або має час існування
обмежений іншим таким об'єктом
Безпека виняткових ситуацій == ES
Полегшує ранній вихід із функції чи циклу
4. Переваги RAII над збирачем
сміття (GC)
Уніформне керування ресурсами:
оперативна пам'ять, потік (thread), відкритий
сокет, відкритий файл, заблокований
мʼютекс, з'єднання із БД
Передбачуваний час існування об'єкта
Сміття
Ефективне використання пам'яті
Відсутність неконтрольованих затримок для
видалення сміття
5. Переваги збирача сміття над RAII
GC простіше використовувати в простих
ситуаціях
GC дозволяє ефективно реалізувати деякі
постійні (persistent) структури даних
RAII вимагає дисципліни розробника
Код багатьох програм написаний на древньому
C++, частково на C
Видалення двічі (UB)
Розіменування “висячого” вказівника (UB)
Протікання пам'яті
Навіть коректний код складно
модифікувати/рефакторити
23. Ефективність unique_ptr
Реалізація unique_ptr використовує Empty
base optimization (EBO)
Кожна операція над unique_ptr теоретично
повинна бути такою ж швидкою, як
відповідна операція над “голим” вказівником
template <class T, class Deleter = std::default_delete<T>>
class unique_ptr;
sizeof(std::unique_ptr<T, Deleter>) == sizeof(T *)
// (якщо Deleter — порожній клас)
24. unique_ptr чи стек?
Варто оголошувати об'єкти на стеку у
функціях та за значенням у класах коли це
можливо: вбудовані типи, класи стандартної
бібліотеки, інші прості структури та класи. Це
простіше і ефективніше.
Поліморфний об'єкт
Вказівник на реалізацію (класу) ==
Pointer to implementation == Pimpl idiom ==
Opaque pointer
25. unique_ptr чи стек? (2)
Потрібен стан відсутності - nullptr (краще
std::optional із C++17)
Адреса об'єкта повинна бути сталою, але
власник об'єкта може змінюватись
Переміщення об'єкту повільне або
неможливе
Потрібне особливе видалення (custom
deleter)
29. std::auto_ptr
Доступний в C++98
Не інтуїтивна семантика копіювання
Не може бути використаний в контейнерах
Deprecated in C++11
Removed in C++17
Може бути легко замінений на unique_ptr
30. Приклад “Вказівник на реалізацію”
1 // Spacer.h
2 class Spacer
3 {
4 public:
5 Spacer();
6 ~Spacer();
7 // ...
8 private:
9 class Impl;
10 std::unique_ptr<Impl> impl_;
11 };
12
13 // Spacer.cpp
14 class Spacer::Impl
15 {
16 // ...
17 };
18 Spacer::Spacer() : impl_(std::make_unique<Impl>()) {}
19 // Destructor definition must be in the cpp file because the
20 // implicitly invoked Impl's destructor needs complete type
21 Spacer::~Spacer() = default;
31. Спеціалізація для масивів
Зазвичай std::vector або std::string зручніші.
Функція, яку не можна змінити (бібліотечна
функція), приймає/вертає вказівник на масив
unique_ptr<T[]> ефективніший в деяких
ситуаціях:
new T[n] виділяє пам'ять для рівно n елементів
new T[n] не ініціює елементи, якщо T - POD
template <class T, class Deleter> class unique_ptr<T[], Deleter>;