Сколько памяти занимает std::shared_ptr?

Ответ

Размер самого объекта std::shared_ptr<T> обычно равен двум указателям (например, 16 байт на 64-битной системе). Однако общие накладные расходы выше из-за динамически выделяемого блока управления (control block).

Структура:

  1. Указатель на управляемый объект.
  2. Указатель на блок управления. Этот блок содержит:
    • Атомарный счетчик сильных ссылок (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) — сразу подозрение, что человек нихуя не понимает.