смартпоинтеры - реализация 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