Какие реализации смарт-указателя C ++ доступны?

121

Сравнение, плюсы, минусы и когда использовать?

Это побочный продукт ветки сборки мусора, где то, что я считал простым ответом, вызвало много комментариев о некоторых конкретных реализациях интеллектуальных указателей, поэтому казалось, что стоит начать новый пост.

В конечном итоге вопрос заключается в том, каковы различные реализации интеллектуальных указателей в C ++ и как они сравниваются? Просто простые плюсы и минусы или исключения и подводные камни к тому, что, по вашему мнению, должно работать.

Я опубликовал несколько реализаций, которые я использовал или, по крайней мере, замалчивал и рассматривал возможность использования в качестве ответа ниже, и мое понимание их различий и сходств, которые могут быть не на 100% точными, поэтому не стесняйтесь проверять факты или исправлять меня по мере необходимости.

Цель состоит в том, чтобы узнать о некоторых новых объектах и ​​библиотеках или исправить мое использование и понимание существующих реализаций, которые уже широко используются, и в итоге получить достойную ссылку для других.

AJG85
источник
5
Я думаю, что это следует повторно опубликовать как ответ на этот вопрос, и вопрос превратить в настоящий вопрос. В противном случае я чувствую, что люди закроют это как «не настоящий вопрос».
Strager
3
Есть множество других интеллектуальных указателей, например интеллектуальные указатели ATL или OpenSceneGraphosg::ref_ptr .
Джеймс Макнеллис
11
Здесь есть вопрос?
Коди Грей
6
Я думаю, что вы неправильно поняли std::auto_ptr. std::auto_ptr_refэто деталь дизайна std::auto_ptr. std::auto_ptrне имеет ничего общего со сборкой мусора, его основная цель - разрешить безопасную передачу прав собственности в исключительных случаях, особенно в ситуациях вызова и возврата функций. std::unique_ptrможет решить только упомянутые вами «проблемы» со стандартными контейнерами, потому что C ++ изменился, чтобы позволить различать перемещение и копирование, а стандартные контейнеры были изменены, чтобы воспользоваться этим.
CB Bailey
3
Вы говорите, что не являетесь экспертом в умных указателях, но ваше резюме довольно исчерпывающее и правильное (за исключением незначительной придирки, касающейся auto_ptr_refдеталей реализации). Тем не менее, я согласен с тем, что вы должны опубликовать это как ответ и переформулировать вопрос, чтобы он стал актуальным. Это может затем послужить ссылкой на будущее.
Конрад Рудольф

Ответы:

231

C ++ 03

std::auto_ptr- Возможно, один из оригиналов страдал синдромом первого черновика, но с ограниченными возможностями по сбору мусора. Первым недостатком является то, что он вызывает deleteразрушение, что делает их неприемлемыми для хранения объектов, выделенных массивом ( new[]). Он становится владельцем указателя, поэтому два автоматических указателя не должны содержать один и тот же объект. Присваивание передаст право собственности и сбросит автоматический указатель rvalue на нулевой указатель. Что приводит, пожалуй, к худшему недостатку; они не могут использоваться в контейнерах STL из-за вышеупомянутой невозможности копирования. Последний удар по любому варианту использования заключается в том, что они будут исключены из следующего стандарта C ++.

std::auto_ptr_ref- Это не умный указатель, это на самом деле деталь конструкции, используемая в сочетании с std::auto_ptrвозможностью копирования и назначения в определенных ситуациях. В частности, его можно использовать для преобразования неконстантного значения std::auto_ptrв lvalue с помощью трюка Колвина-Гиббонса, также известного как конструктор перемещения для передачи владения.

Напротив, возможно, на std::auto_ptrсамом деле он не предназначался для использования в качестве универсального интеллектуального указателя для автоматической сборки мусора. Большинство моих ограниченных представлений и предположений основаны на «Эффективном использовании auto_ptr» Херба Саттера, и я использую его регулярно, хотя и не всегда наиболее оптимизированным образом.


C ++ 11

std::unique_ptr- Это наш друг, который заменит std::auto_ptrего, будет очень похоже, за исключением ключевых улучшений, направленных на исправление слабых мест, таких std::auto_ptrкак работа с массивами, защита lvalue с помощью частного конструктора копирования, возможность использования с контейнерами и алгоритмами STL и т. Д. Поскольку это накладные расходы на производительность и объем памяти ограничен, это идеальный кандидат для замены, или, возможно, более точно описанного как владение необработанными указателями. Поскольку «уникальный» подразумевает, что есть только один владелец указателя, как и предыдущий std::auto_ptr.

std::shared_ptr- Я считаю, что это основано на TR1, boost::shared_ptrно улучшено, чтобы включить арифметику сглаживания и указателя. Короче говоря, он обертывает интеллектуальный указатель с подсчетом ссылок вокруг динамически выделяемого объекта. Поскольку «общий» подразумевает, что указатель может принадлежать более чем одному общему указателю, когда последняя ссылка последнего общего указателя выходит за пределы области видимости, объект будет соответствующим образом удален. Они также являются потокобезопасными и в большинстве случаев могут обрабатывать неполные типы. std::make_sharedможет использоваться для эффективного создания std::shared_ptrс одним распределением кучи с использованием распределителя по умолчанию.

std::weak_ptr- Аналогично на основе TR1 и boost::weak_ptr. Это ссылка на объект, принадлежащий a, std::shared_ptrи поэтому не предотвратит удаление объекта, если std::shared_ptrсчетчик ссылок упадет до нуля. Чтобы получить доступ к необработанному указателю, вам сначала нужно получить доступ к нему std::shared_ptrпутем вызова, lockкоторый вернет пустое значение, std::shared_ptrесли срок действия указанного указателя истек и он уже был уничтожен. Это в первую очередь полезно, чтобы избежать неопределенного зависания счетчика ссылок при использовании нескольких интеллектуальных указателей.


Увеличение

boost::shared_ptr- Вероятно, самый простой в использовании в самых различных сценариях (STL, PIMPL, RAII и т. Д.). Это умный указатель с подсчетом общих ссылок. Я слышал несколько жалоб на производительность и накладные расходы в некоторых ситуациях, но я, должно быть, проигнорировал их, потому что не могу вспомнить, о чем был спор. По-видимому, он был достаточно популярен, чтобы стать ожидающим стандартным объектом C ++, и никаких недостатков по сравнению с нормой в отношении интеллектуальных указателей не возникает.

boost::weak_ptr- Подобно предыдущему описанию std::weak_ptr, основанному на этой реализации, это позволяет ссылаться на файл boost::shared_ptr. Неудивительно, что вы вызываете lock()для доступа «сильный» общий указатель и должны проверить, чтобы убедиться, что он действителен, поскольку он уже мог быть уничтожен. Просто убедитесь, что вы не сохранили возвращенный общий указатель, и позвольте ему выйти из области видимости, как только вы закончите с ним, иначе вы вернетесь к проблеме циклических ссылок, когда ваши счетчики ссылок будут зависать, а объекты не будут уничтожены.

boost::scoped_ptr- Это простой класс интеллектуальных указателей с небольшими накладными расходами, вероятно, разработанный для более эффективной альтернативы, чем boost::shared_ptrкогда можно использовать. Это сравнимо с std::auto_ptrтем, что его нельзя безопасно использовать как элемент контейнера STL или с несколькими указателями на один и тот же объект.

boost::intrusive_ptr- Я никогда не использовал это, но, насколько я понимаю, он предназначен для использования при создании ваших собственных классов, совместимых с интеллектуальным указателем. Вам необходимо реализовать подсчет ссылок самостоятельно, вам также потребуется реализовать несколько методов, если вы хотите, чтобы ваш класс был универсальным, кроме того, вам нужно будет реализовать собственную безопасность потоков. С другой стороны, это, вероятно, дает вам наиболее индивидуальный способ выбора и выбора того, сколько или как мало «умности» вы хотите. intrusive_ptrобычно более эффективен, чем shared_ptrпоскольку он позволяет вам выделять одну кучу для каждого объекта. (спасибо Арвид)

boost::shared_array- Это boost::shared_ptrдля массивов. В основном new [], operator[]и, конечно delete []же, встроены. Это можно использовать в контейнерах STL, и, насколько я знаю, делает все boost:shared_ptr, хотя вы не можете использовать boost::weak_ptrс ними. Однако в качестве альтернативы вы можете использовать a boost::shared_ptr<std::vector<>>для аналогичных функций и восстановить возможность использования boost::weak_ptrдля ссылок.

boost::scoped_array- Это boost::scoped_ptrдля массивов. Как и в случае со boost::shared_arrayвсем необходимым массивом, в нем заложено добро. Этот массив не копируется и поэтому не может использоваться в контейнерах STL. Я нашел почти везде, где вы хотели бы использовать это, вы, вероятно, могли бы просто использовать std::vector. Я никогда не определял, что на самом деле быстрее или имеет меньше накладных расходов, но этот массив с ограниченной областью видимости кажется гораздо менее сложным, чем вектор STL. Если вы хотите сохранить выделение в стеке, подумайте boost::arrayвместо этого.


Qt

QPointer- Представленный в Qt 4.0, это «слабый» интеллектуальный указатель, который работает только с QObjectклассами и производными классами, которые в структуре Qt являются почти всем, так что это не является ограничением. Однако есть ограничения, а именно то, что он не предоставляет «сильный» указатель, и хотя вы можете проверить, действителен ли базовый объект, isNull()вы можете обнаружить, что ваш объект уничтожается сразу после прохождения этой проверки, особенно в многопоточных средах. Я считаю, что люди Qt считают это устаревшим.

QSharedDataPointer- Это «сильный» интеллектуальный указатель, потенциально сопоставимый с boost::intrusive_ptrнекоторыми встроенными средствами безопасности потоков, но он требует, чтобы вы включили методы подсчета ссылок ( refи deref), которые вы можете сделать путем создания подклассов QSharedData. Как и в случае с большей частью Qt, объекты лучше всего использовать через широкое наследование и создание подклассов, все кажется предполагаемым дизайном.

QExplicitlySharedDataPointer- Очень похоже на, QSharedDataPointerза исключением того, что он не вызывает неявно detach(). Я бы назвал эту версию 2.0, QSharedDataPointerпоскольку небольшое усиление контроля над тем, когда именно отсоединять после того, как счетчик ссылок упадет до нуля, не стоит особого смысла в новом объекте.

QSharedPointer- Атомарный подсчет ссылок, потокобезопасность, разделяемый указатель, настраиваемые удаления (поддержка массивов), похоже на все, что должно быть у интеллектуального указателя. Это то, что я в основном использую в качестве интеллектуального указателя в Qt, и я считаю его сопоставимым с, boost:shared_ptrхотя, вероятно, значительно большими накладными расходами, как и многие объекты в Qt.

QWeakPointer- Чувствуете повторяющийся узор? Так же, как std::weak_ptrи boost::weak_ptrэто используется в сочетании с тем, QSharedPointerкогда вам нужны ссылки между двумя интеллектуальными указателями, которые в противном случае привели бы к тому, что ваши объекты никогда не будут удалены.

QScopedPointer- Это имя также должно показаться знакомым и фактически основано на boost::scoped_ptrверсиях общих и слабых указателей, в отличие от Qt. Он функционирует для предоставления единого интеллектуального указателя владельца без дополнительных затрат, QSharedPointerчто делает его более подходящим для совместимости, безопасного кода исключений и всего того, что вы можете использовать std::auto_ptrили boost::scoped_ptrдля чего.

AJG85
источник
1
Я думаю, стоит упомянуть две вещи: intrusive_ptrкак правило, более эффективен, чем shared_ptr, поскольку он позволяет вам выделять одну кучу для каждого объекта. shared_ptrв общем случае выделяет отдельный небольшой объект кучи для счетчиков ссылок. std::make_sharedможно использовать, чтобы получить лучшее из обоих миров. shared_ptrс одним выделением кучи.
Arvid
У меня, возможно, не связанный с этим вопрос: можно ли реализовать сборку мусора, просто заменив все указатели на shared_ptrs? (Не считая разрешения циклических ссылок)
Сет Карнеги
@Seth Carnegie: Не все указатели будут указывать на что-то, размещенное в бесплатном магазине.
In silico
2
@the_mandrill Но это работает, если деструктор класса-владельца определен в отдельной единице трансляции (.cpp-файле), чем клиентский код, который в Pimpl-идиоме все равно дается. Поскольку эта единица трансляции обычно знает полное определение Pimpl, и поэтому ее деструктор (когда он уничтожает auto_ptr) правильно уничтожает Pimpl. У меня тоже были опасения по этому поводу, когда я увидел эти предупреждения, но я попробовал, и он работает (вызывается деструктор Pimpl). PS: пожалуйста, используйте @ -синтаксис, чтобы я видел ответы.
Christian Rau
2
Повышена полезность списка за счет добавления соответствующих ссылок на документы.
ulidtko
5

Также существует Loki, который реализует интеллектуальные указатели на основе политик.

Другие ссылки на интеллектуальные указатели на основе политик, посвященные проблеме плохой поддержки оптимизации пустой базы наряду с множественным наследованием многими компиляторами:

Грегори Пакош
источник
1

Помимо приведенных, есть еще несколько ориентированных на безопасность:

SaferCPlusPlus

mse::TRefCountingPointer- это умный указатель с подсчетом ссылок, например std::shared_ptr. Разница в том, что mse::TRefCountingPointerон безопаснее, меньше и быстрее, но не имеет механизма безопасности потоков. И он поставляется в «ненулевой» и «фиксированной» (без перенацеливания) версиях, которые можно смело предположить, что они всегда указывают на правильно выделенный объект. Итак, в основном, если ваш целевой объект совместно используется асинхронными потоками, используйте std::shared_ptr, в противном случае mse::TRefCountingPointerболее оптимально.

mse::TScopeOwnerPointerпохож на boost::scoped_ptr, но работает в сочетании с mse::TScopeFixedPointerотношениями "сильный-слабый" указатель, например std::shared_ptrи std::weak_ptr.

mse::TScopeFixedPointerуказывает на объекты, которые размещены в стеке или чей указатель-владелец выделен в стеке. Функциональность (намеренно) ограничена для повышения безопасности во время компиляции без затрат времени выполнения. Смысл указателей «области действия» состоит в том, чтобы идентифицировать набор обстоятельств, которые достаточно просты и детерминированы, чтобы не было необходимости в механизмах безопасности (во время выполнения).

mse::TRegisteredPointerведет себя как необработанный указатель, за исключением того, что его значение автоматически устанавливается в null_ptr при уничтожении целевого объекта. Его можно использовать как общую замену необработанным указателям в большинстве ситуаций. Как и необработанный указатель, он не имеет внутренней потокобезопасности. Но взамен у него нет проблем с нацеливанием на объекты, размещенные в стеке (и с получением соответствующего преимущества в производительности). Когда проверки во время выполнения включены, этот указатель защищен от доступа к недопустимой памяти. Поскольку он mse::TRegisteredPointerведет себя так же, как необработанный указатель при указании на допустимые объекты, он может быть «отключен» (автоматически заменен соответствующим необработанным указателем) с помощью директивы времени компиляции, что позволяет использовать его для выявления ошибок при отладке / тестировании. / beta, не неся накладных расходов в режиме выпуска.

Вот статья, в которой рассказывается, зачем и как их использовать. (Заметьте, бессовестная вилка.)

Ной
источник