Я в целом согласен с ответом Джеймса Макнеллиса. Однако стоит упомянуть еще один момент.
Как вы, возможно, знаете, shared_ptr<T>
также может использоваться, когда тип T
определен не полностью.
То есть:
class AbraCadabra;
boost::shared_ptr<AbraCadabra> myPtr;
// ...
Это скомпилирует и сработает. В отличие от многих других реализаций интеллектуальных указателей, которые фактически требуют, чтобы инкапсулированный тип был полностью определен для их использования. Это связано с тем, что интеллектуальный указатель должен знать, что нужно удалить инкапсулированный объект, когда на него больше нет ссылок, и чтобы удалить объект, один должен знать, что это такое.
Это достигается с помощью следующего трюка: shared_ptr
на самом деле состоит из следующего:
- Непрозрачный указатель на объект
- Общие счетчики ссылок (что описал Джеймс МакНеллис)
- Указатель на выделенную фабрику , которая знает, как уничтожить ваш объект.
Вышеуказанная фабрика является вспомогательным объектом с одной виртуальной функцией, которая должна правильно удалять ваш объект.
Эта фабрика фактически создается, когда вы присваиваете значение для вашего общего указателя.
То есть следующий код
AbraCadabra* pObj = /* get it from somewhere */;
myPtr.reset(pObj);
Здесь находится эта фабрика.
Примечание: функция reset
на самом деле является функцией template . Это фактически создает фабрику для указанного типа (тип объекта, передаваемого в качестве параметра).
Здесь ваш тип должен быть полностью определен. То есть, если он все еще не определен - вы получите ошибку компиляции.
Также обратите внимание: если вы на самом деле создаете объект производного типа (производного от AbraCadabra
) и присваиваете ему shared_ptr
- он будет удален правильным образом, даже если ваш деструктор не является виртуальным.
shared_ptr
всегда удаляет объект в соответствии с типом, который видит в функции reset
.
Так что shared_ptr - довольно сложный вариант умного указателя. Это дает потрясающую гибкость . Однако вы должны знать, что такая гибкость достигается за цену чрезвычайно плохой производительности по сравнению с другими возможными реализациями интеллектуального указателя.
С другой стороны - существуют так называемые «навязчивые» умные указатели. Они не обладают такой гибкостью, однако, напротив, они обеспечивают лучшую производительность.
Плюсы shared_ptr
по сравнению с навязчивыми умными указателями:
- Очень гибкое использование. Определять инкапсулированный тип нужно только при назначении его
shared_ptr
. Это очень ценно для больших проектов, значительно уменьшает зависимости.
- Инкапсулированный тип не должен иметь виртуального деструктора, однако полиморфные типы будут удаляться корректно.
- Может использоваться со слабыми указателями.
Минусы shared_ptr
по сравнению с навязчивыми умными указателями:
- Очень варварская производительность и потеря кучи памяти. При назначении выделяется еще 2 объекта: счетчики ссылок плюс заводские (растрата памяти, медленный). Это, однако, происходит только на
reset
. Когда один shared_ptr
назначен другому - больше ничего не выделяется.
- Вышеуказанное может вызвать исключение. (нехватка памяти). Напротив, интрузивные умные указатели могут никогда не выдавать (кроме исключений процесса, связанных с неправильным доступом к памяти, переполнением стека и т. Д.)
- Удаление вашего объекта также происходит медленно: необходимо освободить еще две структуры.
При работе с навязчивыми умными указателями вы можете свободно смешивать умные указатели с необработанными. Это нормально, потому что фактический счетчик ссылок находится внутри самого объекта, который является единичным.
Для сравнения: с shared_ptr
вы можете , а не смешивать с необработанными указателями.
AbraCadabra * pObj = / * получить его откуда-то * /;
myPtr.reset (PObj);
// ...
pObj = myPtr.get ();
boost :: shared_ptr myPtr2 (pObj); // упс
Выше будет сбой.