Since there there's only one allocation, the pointee's memory cannot be deallocated until the control block is no longer in use. A weak_ptr can keep the control block alive indefinitely.
В общем я так понял что weak_ptr не обещает корректности если шареный указатель создан через make_shared. Не слабо!
поведение weak_ptr вполне себе корректно.
под капотом есть два счетчика: количество сильных ссылок и слабых.
кроме этого - указатель на сам объект-ресурс смарт-поинтера.
1.
случай с двумя аллокациями:
shared_ptr<resource> sm (new resource);
имеем два указателя: на счетчики, и на объект-ресурс.
когда количество сильных ссылок станет равным нулю,
будет запущен деструктор объекта-ресурса.
затем, будет освобождена память под объект-ресурс.
память под счетчики будет освобождена только и только тогда,
когда количество и сильных, и слабых ссылок будет равным нулю.
таким образом, последний шаред_птр грохнет объект-ресурс, и освободит его память.
последний смарт-поинтер (шаред или вик) грохнет объект-счетчиков, и освободит его память.
в случае, если в памяти где то будет оставаться живой шаред_птр,
то в памяти будет висеть и объект-ресурс, и объект-счетчиков.
в случае, если в памяти где то будет оставаться живой вик_птр,
то в памяти будет висеть объект-счетчиков.
недостатки:
две аллокации - две дорогостоящие операции выделея,
и две ещё более дорогостоящих операций по освобождению.
при этом, объект-ресурс и объект-счетчик разбрасываются по самым разным адресам,
что провоцирует кэш-миссы.
(при доступе к ним, придется постоянно подгружать то одну, то другую страницы памяти).
2.
случай с одной аллокацией:
shared_ptr<resource> sm = std::make_shared<resource>();
make_shared разом выделяет большой кусок,
в котором располагает и объект-счетчиков, и объект-ресурс.
например, так:
[ [счетчик сильных ссылок][счетчик слабых ссылок][ [ресурс ] ]
sizeof(uint32_t) sizeof(uint32_t) sizeof(resource)
зная размеры всех задействованных типов (размеры счетчиков, и размер ресурса),
и принцип упаковки, можно легко получать доступ к счетчикам,
или к объекту-ресурсу, расположенному в едином блоке памяти.
данный способ более эффективный, по сравнению с первым случаем:
меньше дорогостоящих операций выделения памяти,
объекты аллоцируются рядышком, в непрерывном блоке памяти, а не разбрасываются по всей памяти,
что является кэш-френдли.
аналогично первому случаю, последний шаред_птр грохнет объект-ресурс (позовет деструктор)
но пока счетчик слабых ссылок ещё не обнулился, нельзя удалять саму память.
иначе, вик_птр станут неконсистентными.
таким образом:
пока есть хотя бы один живой шаред_птр, то ресурс будет живой.
если нет ни одного шаред_птр, то ресурс будет уничтожен,
но сама память будет висеть до тех пор, пока ещё остается хотя бы один вик_птр.
резюмируя:
в 1 случае память частями выделяется, и частями освобождается.
во 2 случая память выделяется целиком за раз, и так же за раз целиком освобождается.
на мой взгляд, 2й способ работы со смарт-поинтерами предпочтительнее, нежели первый.
зная немножко, как оно там устроенно внутри, я понимаю, что 2й способ работает эффективнее.
но если пренебречь этой эффективностью,
то с точки зрения использования особой разницы нет.