Вы можете конвертировать любые 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, причем источник указывает на ничто непосредственно перед запуском деструктора. Это, в двух словах, радость семантики перемещения! : -)