cpp make_shared для пустых указателей - PullRequest
1 голос
/ 27 декабря 2011

Я хотел бы использовать std :: make_shared для создания пустого указателя.Так как make_shared должен быть быстрее, чем shared_ptr (новый T), и исключение сохраняется, мне интересно, есть ли библиотечная функция для создания shared_ptr (новый foo) способом make_shared.

1 Ответ

14 голосов
/ 27 декабря 2011

Вы можете конвертировать любые shared_ptr<foo> в shared_ptr<void> без потери эффективности, связанной с make_shared:

#include <memory>

struct foo {};

int main()
{
    std::shared_ptr<void> p = std::make_shared<foo>();
}

Преобразование сохраняет foo и счетчик ссылок в одном и том же распределении памяти, даже если вы теперь обращаетесь к нему через void*.

Обновление

Как это работает?

Общая структура std::shared_ptr<foo> состоит из двух указателей:

                          +------> foo
                          |         ^
p1  ---------> (refcount, +)        |
p2  --- foo* -----------------------+

p1 указывает на блок управления, содержащий счетчик ссылок (фактически два счетчика ссылок: один для сильных владельцев и один для слабых владельцев), средство удаления, распределитель и указатель на «динамический» тип объекта , «Динамический» тип - это тип объекта, который видел конструктор shared_ptr<T>, скажем, Y (который может совпадать или не совпадать с T).

p2 имеет тип T*, где T - это то же T, что и в shared_ptr<T>. Думайте об этом как о «статическом» типе хранимого объекта. Когда вы разыменовываете shared_ptr<T>, разыменовывается p2. Когда вы уничтожаете shared_ptr<T>, и если счетчик ссылок становится равным нулю, именно указатель в блоке управления помогает уничтожить foo.

На приведенной выше диаграмме блок управления и foo распределяются динамически. p1 является указателем-владельцем, а указатель в блоке управления является указателем-владельцем. p2 - это не принадлежащий указатель. p2 только - функция разыменования (оператор стрелки, get() и т. Д.).

Когда вы используете make_shared<foo>(), реализация имеет возможность поместить foo прямо в блок управления, наряду со счетчиками ссылок и другими данными:

p1  ---------> (refcount, foo)
p2  --- foo* --------------^

Оптимизация здесь заключается в том, что теперь существует только одно выделение: управляющий блок, который теперь включает foo.

Когда все вышеперечисленное преобразуется в shared_ptr<void>, все, что происходит, это:

p1  ---------> (refcount, foo)
p2  --- void* -------------^

т.е. Тип p2 изменяется с foo* на void*. Вот и все. (помимо увеличения / уменьшения счетчика ссылок для учета копии и уничтожения временного - что может быть исключено конструкцией из значения r). Когда счетчик ссылок становится равным нулю, блок управления по-прежнему уничтожает foo, найденный с помощью p1. p2 не участвует в операции уничтожения.

p1 фактически указывает на общий базовый класс блока управления. Этот базовый класс не знает типа foo, хранящегося в производном блоке управления. Производный блок управления создается в конструкторе shared_ptr в то время, когда известен фактический тип объекта Y. Но с этого момента shared_ptr может связываться с блоком управления только через control_block_base*. Таким образом, такие вещи, как разрушение, происходят посредством вызова виртуальной функции.

"Конструкция перемещения" shared_ptr<void> из значения shared_ptr<foo> в C ++ 11 просто копирует два внутренних указателя и не должна манипулировать счетчиком ссылок. Это потому, что значение shared_ptr<foo> собирается все равно уйти:

// shared_ptr<foo> constructed and destructed within this statement
std::shared_ptr<void> p = std::make_shared<foo>();

Это наиболее ясно видно из исходного кода конструктора shared_ptr:

template<class _Tp>
template<class _Yp>
inline _LIBCPP_INLINE_VISIBILITY
shared_ptr<_Tp>::shared_ptr(shared_ptr<_Yp>&& __r,
                            typename enable_if<is_convertible<_Yp*, _Tp*>::value, __nat>::type)
         _NOEXCEPT
    : __ptr_(__r.__ptr_),
      __cntrl_(__r.__cntrl_)
{
    __r.__ptr_ = 0;
    __r.__cntrl_ = 0;
}

До конструкции преобразования счетчик ссылок равен только 1. А после конструкции преобразования счетчик ссылок по-прежнему равен 1, причем источник указывает на ничто непосредственно перед запуском деструктора. Это, в двух словах, радость семантики перемещения! : -)

...