смартпоинтеры - реализация RAII для выделения дин. памяти
все смарт поинтеры не защищают от создания >1 смартпоинтенов от обычного поинтера
можно создавать смартпоинтер от Base от смартпоинтера на Derived
для смартпоинтеров есть обёртки над кастами

unique_ptr
на самом деле оператор * возвращает std::add_lvalue_reference<T>::type, чтобы можно было сделать std::unique_ptr<void>, void& - нельзя, а std::add_lvalue_reference<void> -> void
можно попросить unique_ptr вызывать не delete, а что-то более умное, ведь освобождение ресурсов не всегда заключается только в освобождении памяти
std::default_delete<T> - функтор
Deleter хранится как объект поля, но до C++20 оптимизировалось хранение через EBO, unique_ptr был приватным наследником, а с C++20 написано [[no_unique_address]]
в shared_ptr и weak_ptr Deleter - не шаблонный параметр, но у него есть шаблонный конструктор, принимающий Deleter в рантайме один Deleter подменяется другим при присваивании
std::make_unique (C++14), std::make_shared (C++11)
f(unique_ptr<int>(new int(5), g()); - в C++ нет гарантии порядка вычисления аргументов функции. то есть может быть так, что сначала бы выделились 5 интов, потом вызвалась g(), а только потом вызвался конструктор для unique_ptr. это плохо, потому что g() могла кинуть исключение. тогда unique_ptr не защитит от утечки памяти. поэтому была придумана make_unique, которая защищена от таких ситуаций
auto pnumbers = std::make_unique<int[]>(10); - массив на 10 интов
.reset() - освобождает память и присваивает nullptr .reset(new int[5]) - освобождает тот и указывает на новые данные
изначально std::make_shared придумали для того, чтобы экономить 1 вызов new, в отличие от явного создания shared_ptr (сначала вызовет new на объект, а потом new в конструкторе на счётчик)
std::make_shared выделяет память на control block - структуру, в которой хранится объект и счётчик
sizeof(shared_ptr) == 16
auto p = std::shared_ptr<int>(new int(5));
auto p2 = std::make_shared<int>(5);разница в том, что в первом случае мы сами выделяем память под объект и уже от готового поинтера вызываем конструктор а во втором случае мы просим создать засшаренный объект типа int из аргументов конструктора 5 и пусть shared_ptr сам выделит и память, и счетчик второй случай чаще используется для создания shared_ptr
shared_ptr
в полях мы храним указатель на объект и указать на control block. если мы создавались через обычный конструктор, то создаётся control block и счетчики в нём инициализируются. а если мы создавались через make_shared, то указать на control block будет nullptr, и мы будем знать оффсет чтобы найти счётчики от T*
в деструкторе shared_ptr нужно проверять равен ли 0 shared_count, если да, то вызвать деструктор T, потом нужно проверить равен ли 0 weak_counter. если и он стал равен 0, то удалить control block и освободить память. но если shared_count = 0, а weak_count != 0, то control block удалять не нужно
при создании через make_shared память из под T не будет освобождена до того, как не умрёт последний weak_ptr
std::enable_shared_from_this - класс, позволяющий делать shared_ptr от себя же, не создавая новое семейство shared_ptr популярный пример CRTP в std чтобы делать shared_ptr от себя, нужно приватно унаследоваться от std::enable_shared_from_this<тип this>. после этого появляется метод shared_from_this();
если shared_from_this был вызван на объекте, которым не владел shared_ptr, то бросается исключение std::bad_weak_ptr

weak_ptr
слабый поинтер - такой поинтер, который не учитывается при подсчёте ссылок на объект. не способен защитить от удаления объекта
если у нас есть циклические зависимости между shared_ptr, то мы их никогда не удалим
weak_ptr нельзя разыменовать можно сделать из weak_ptr shared_ptr с помощью метода .lock() - создаёт новый shared_ptr, указывающий на тот же объект. если weak_ptr был просрочен, то создасться пустой shared_ptr
как weak_ptr понять, что объект под ним был удалён?
в деструкторе weak_ptr нам надо проверять нули ли shared_counter и weak_counter. если да, то удаляем control block