🧬 Двойная цена
Мы все используем умные указатели. Но то, как вы их создаете, кардинально меняет работу с памятью под капотом.
Встречали такой код?
Кажется, всё логично: выделили память через
⚙️ Анатомия
1. Сам объект (ваши данные
2. Контрольный блок (Control Block) - служебная структура, где лежат счетчики ссылок (reference count) и счетчики
Когда вы пишете
1. Отрабатывает
2. Конструктор
Два системных вызова. Фрагментация кучи (heap). Промахи кэша процессора, потому что объект и счетчик лежат в разных концах памяти.
🚀 Решение:
Что делает
Плюсы:
• В 2 раза меньше аллокаций. Код работает быстрее.
• Cache Locality. Объект и счетчик ссылок лежат в памяти впритык друг к другу. Процессор это обожает.
• Безопасность. До C++17 старый подход с
🦇 Темная сторона (О чем не пишут в туториалах)
Есть ровно один случай, когда
Если вы удалили все
А так как
Если ваш объект весит 500 Мегабайт - вы получите «фантомную» утечку памяти.
💡В 99% случаев используйте
#cpp #memory #pointers #optimization #sharedptr #coding #tips
➡️ @cpp_geek
std::shared_ptr: Почему профи всегда пишут make_shared?Мы все используем умные указатели. Но то, как вы их создаете, кардинально меняет работу с памятью под капотом.
Встречали такой код?
// 🐢 ПЛОХО: Классический подход
std::shared_ptr<User> user(new User());
Кажется, всё логично: выделили память через
new, передали в shared_ptr. Но на деле вы заставляете программу сделать две аллокации (выделения памяти) вместо одной.⚙️ Анатомия
shared_ptrstd::shared_ptr состоит из двух частей:1. Сам объект (ваши данные
User).2. Контрольный блок (Control Block) - служебная структура, где лежат счетчики ссылок (reference count) и счетчики
weak_ptr.Когда вы пишете
std::shared_ptr<User>(new User()), происходит следующее:1. Отрабатывает
new User() - программа идет к ОС и просит кусок памяти.2. Конструктор
shared_ptr видит сырой указатель, понимает, что ему нужен Контрольный блок, и еще раз идет к ОС за вторым куском памяти.Два системных вызова. Фрагментация кучи (heap). Промахи кэша процессора, потому что объект и счетчик лежат в разных концах памяти.
🚀 Решение:
std::make_shared
// 🚀 ХОРОШО: Единый блок памяти
auto user = std::make_shared<User>();
Что делает
make_shared? Он считает размер вашего объекта User + размер Контрольного блока, и просит у операционной системы один большой кусок памяти за один раз. Плюсы:
• В 2 раза меньше аллокаций. Код работает быстрее.
• Cache Locality. Объект и счетчик ссылок лежат в памяти впритык друг к другу. Процессор это обожает.
• Безопасность. До C++17 старый подход с
new мог привести к утечке памяти, если функция принимала несколько аргументов и один из них бросал исключение. С make_shared это исключено.🦇 Темная сторона (О чем не пишут в туториалах)
Есть ровно один случай, когда
make_shared может навредить. Это связано со слабыми указателями (std::weak_ptr).Если вы удалили все
shared_ptr, вызывается деструктор объекта User. Но если остался хотя бы один weak_ptr, Контрольный блок обязан жить! А так как
make_shared склеил Контрольный блок и объект в один кусок памяти, оперативная память из-под объекта User не вернется системе, пока жив weak_ptr (даже если сам объект уже "мертв" и деструктор отработал). Если ваш объект весит 500 Мегабайт - вы получите «фантомную» утечку памяти.
💡В 99% случаев используйте
std::make_shared. Используйте new std::shared_ptr только если у вас гигантские объекты, на которые подолгу смотрят «зависшие» weak_ptr, или если вам нужен кастомный удалитель (custom deleter).#cpp #memory #pointers #optimization #sharedptr #coding #tips
➡️ @cpp_geek
👍5❤1