Каковы правила для конверсий между различными интеллектуальными указателями в С++
В TR1 были введены shared_ptr, weak_ptr, scoped_ptr и unique_ptr и др.
Я хотел бы знать различные правила преобразования/типа продвижения между этими типами.
Например, что происходит, когда scoped_ptr назначается shared_ptr? Являются ли такие преобразования возможными/значимыми и каковы некоторые прецеденты для таких преобразований?
(Есть ли таблица в спецификации?)
Ответы
Ответ 1
Во-первых, несколько поправок к вашему вопросу:
-
scoped_ptr
является частью Boost и не входит ни в С++ TR1, ни в С++ 0x (ожидается, что в С++ 0x unique_ptr
можно использовать, где scoped_ptr
традиционно используется).
-
unique_ptr
не является частью С++ TR1; он является частью С++ 0x (поскольку он полагается на ссылки rvalue и перемещает семантику, которые доступны только в С++ 0x).
Чтобы ответить на ваш вопрос: shared_ptr
и weak_ptr
идут рука об руку. Объект, принадлежащий shared_ptr
, также может ссылаться на weak_ptr
. Они дополняют друг друга.
A unique_ptr
имеет единоличную собственность на объект, которым он управляет; никто другой не может владеть объектом. Это противоположно семантике собственности shared_ptr
: при unique_ptr
у вас есть уникальное уникальное право собственности; с shared_ptr
, которым вы поделились, не уникальное право собственности.
Вы можете построить shared_ptr
из unique_ptr
; когда вы это делаете, unique_ptr
теряет право собственности на объект. Это работает, потому что вы всегда знаете, что данный unique_ptr
всегда является единственным владельцем объекта, поэтому он способен освободить это право собственности.
Как только объект принадлежит shared_ptr
, вы не можете освободить право собственности на объект, потому что нет гарантии, что данный shared_ptr
является единственным владельцем объекта.
Ответ 2
Для двух классов A
и B
(которые могут быть типами интеллектуальных указателей) существует четыре основных способа преобразования экземпляра типа B
в тип A
:
-
A
- это доступный объект базового класса B
(например, B
общедоступен из A
), и преобразование может разрезать или просто отрегулировать тип ссылки или указатель. (преднамеренное протаскивание).
-
A
имеет доступный конструктор с B
.
-
B
имеет доступный оператор преобразования, создающий A
.
-
Существует некоторая функция, которая принимает B
и создает A
, и вы вызываете эту функцию.
Для интеллектуальных указателей наследование не используется для облегчения преобразования, поскольку наследование допускает неправильные преобразования; следовательно, пробивать выше. Например, если SmartPtr<Derived>
наследуется публично из SmartPtr<Base>
, тогда можно было бы сделать SmartPtr<Base>& spBase = spDerived;
, а затем, например, spBase = spOtherDerived
, что было бы довольно проблематично... При достаточно высоком уровне абстракции это по существу та же проблема, что и для const
для конверсий указателей; см. раздел "Вопросы и ответы" 18.17 "Почему я получаю сообщение об ошибке с преобразованием Foo ** → Foo const **?" .
Таким образом, преобразования интеллектуальных указателей обычно выражаются через последние три пункта, а именно: конструкторы, операторы преобразования и названные функции преобразования.
По существу в С++ 0x есть три умных указателя, не считая устаревшего auto_ptr
:
-
std::unique_ptr
для отдельных объектов.
-
std::unique_ptr
для массивов.
-
std::shared_ptr
для отдельных объектов.
unique_ptr
выражает передачу прав собственности, как это делал и делает старый auto_ptr
. Но auto_ptr
не поддерживал массивы. unique_ptr
, и это влияет на возможные преобразования.
Для отдельных объектов unique_ptr
поддерживает преобразования, которые выполняют соответствующие указатели на raw, через конструкторы. У него есть шаблонный конструктор, принимающий unique_ptr
другого типа. См. С++ 0x черновик N3126 §20.9.10.2.
Но для массивов, которые были бы столь же опасны, как и для сырых указателей! И поэтому для массивов unique_ptr
не предлагается базовое/производное преобразование. См. С++ 0x черновик N3126 §20.9.10.3.
Так как unique_ptr
выражает передачу прав, тогда как shared_ptr
выраженное совместное владение не может быть безопасным общим преобразованием от shared_ptr
до unique_ptr
. Однако, в противном случае Boost shared_ptr
имеет конструктор, принимающий auto_ptr
, а С++ 0x shared_ptr
сохраняет это (также имеет его) и, естественно, добавляет конструктор, принимающий unique_ptr
. См. С++ 0x черновик N3126 §20.9.11.2/1.
shared_ptr
предоставляет базовые/производные преобразования через конструкторы и через свободные функции, которые концептуально реализуют "отливки". По существу это означает, что shared_ptr
довольно опасно использовать непосредственно для массивов объектов типа класса. Для этого используйте его.
Преобразование из shared_ptr
в unique_ptr
, как уже упоминалось, не поддерживается как общая операция. Поскольку совместное владение напрямую не совместимо с передачей прав собственности. Однако, невзирая на осложнения, безопасность потока, shared_ptr::unique
сообщает вам, есть ли один владелец (а именно ваш экземпляр), а затем, если у вас есть необходимое знание о том, как был создан начальный shared_ptr
, вы можете использовать свободную функцию get_deleter
для получения указатель на функцию делетера и сделать некоторые махинации низкого уровня. Если вы полностью поймете, о чем я говорю здесь, тогда все хорошо, хорошо. Если нет, то лучше всего, что я не даю более подробных сведений, потому что это очень особый случай, и он требует максимальной осторожности и что вы действительно знаете, что делаете и торгуете;.; -)
Хорошо, что об этом. Я не обсуждаю weak_ptr
, так как это только часть функциональности shared_ptr
. Но, надеюсь, это то, о чем вы просите.
Ответ 3
-
scoped_ptr
AFAIK не входит в TR1 (исправьте меня, если я ошибаюсь). Обычно boost
scoped_ptr
не может передавать право собственности. После того, как вы указали на него указатель, вы не сможете его освободить.
-
unique_ptr
может передавать право собственности только с помощью std::move
, чтобы он не передавал право собственности.
-
shared_ptr
не может освободить право собственности, поскольку другие указатели могут поделиться им. Его можно преобразовать в weak_ptr
. Если вы попытаетесь преобразовать его из weak_ptr
, и объект был выпущен, он будет бросать.
-
weak_ptr
может быть создан из shared_ptr
, при преобразовании в shared_ptr
он может выкинуть, если объект больше не существует.