Ответ
Размер самого объекта std::shared_ptr<T> обычно равен двум указателям (например, 16 байт на 64-битной системе). Однако общие накладные расходы выше из-за динамически выделяемого блока управления (control block).
Структура:
- Указатель на управляемый объект.
- Указатель на блок управления. Этот блок содержит:
- Атомарный счетчик сильных ссылок (
use_count). - Атомарный счетчик слабых ссылок (
weak_count). - Указатель на функцию-делитер (deleter).
- (Опционально) Аллокатор.
- Атомарный счетчик сильных ссылок (
Практический пример и сравнение:
#include <iostream>
#include <memory>
int main() {
// Способ 1: make_shared (оптимизированное выделение)
auto sp1 = std::make_shared<int>(42);
// Объект и блок управления могут быть выделены одним блоком памяти.
std::cout << "Size of shared_ptr object: " << sizeof(sp1) << " bytesn"; // e.g., 16
// Способ 2: Конструктор от сырого указателя (менее эффективно)
auto sp2 = std::shared_ptr<int>(new int(42));
// Здесь два отдельных выделения: для int и для блока управления.
// Для сравнения:
std::unique_ptr<int> up(new int(42));
std::cout << "Size of unique_ptr object: " << sizeof(up) << " bytesn"; // e.g., 8
// unique_ptr не хранит счетчик ссылок, поэтому он легче.
return 0;
}
Итог: shared_ptr — это инструмент с накладными расходами. В performance-critical коде я предпочитаю unique_ptr или слежу за созданием лишних копий shared_ptr.
Ответ 18+ 🔞
Давай разберем эту тему, а то некоторые думают, что shared_ptr — это какая-то магия, которая ничего не стоит. Ёпта, нет, ребята. За всё надо платить.
Сам по себе объект shared_ptr<T> — это, по сути, два указателя в одном флаконе. На 64-битной системе это обычно 16 байт. Не овердохуища, но уже не unique_ptr, который один указатель весит.
Но вся соль не в нём, а в том, что он тянет за собой блок управления (control block). Это такая отдельная динамическая хуйня, которая содержит:
- Счетчик сильных ссылок (
use_count) — атомарный, чтобы в многопоточке не словить рассинхрон. - Счетчик слабых ссылок (
weak_count) — да, и за них тоже надо считать. - Указатель на делитер (функцию удаления) — на случай, если ты свой кастомный делитер прикрутил.
- И иногда ещё аллокатор, если ты совсем извращенец.
Вот тебе наглядный пример, чтобы не быть голословным:
#include <iostream>
#include <memory>
int main() {
// Способ 1: через make_shared (правильный путь, самурай)
auto sp1 = std::make_shared<int>(42);
// Тут компилятор может схитрить и выделить память под объект и блок управления одним куском. Умно, да?
std::cout << "Size of shared_ptr object: " << sizeof(sp1) << " bytesn"; // Выведет, например, 16
// Способ 2: от сырого указателя (путь страдания и лишних аллокаций)
auto sp2 = std::shared_ptr<int>(new int(42));
// А вот тут уже будет ДВЕ отдельных операции new: одна для int, вторая для блока управления. Пиздец как неэффективно.
// Для контраста глянем на нормального пацана:
std::unique_ptr<int> up(new int(42));
std::cout << "Size of unique_ptr object: " << sizeof(up) << " bytesn"; // А тут будет 8. В два раза меньше!
// Потому что ему не надо хранить всю эту хуйню со счетчиками, он сам себе хозяин.
return 0;
}
Итог, чувак: shared_ptr — это как навороченный внедорожник: мощно, удобно, но бензина (памяти и процессорного времени) жрёт дохуя. В коде, где важна производительность, я десять раз подумаю, а не обойтись ли unique_ptr или вообще следить за временем жизни объекта вручную. А если уж юзаешь shared_ptr, то только через make_shared, иначе сам себе злой буратино. Волнение ебать, когда видишь в коде shared_ptr(new T) — сразу подозрение, что человек нихуя не понимает.