Сравнение, плюсы, минусы и когда использовать?
Это побочный продукт ветки сборки мусора, где то, что я считал простым ответом, вызвало много комментариев о некоторых конкретных реализациях интеллектуальных указателей, поэтому казалось, что стоит начать новый пост.
В конечном итоге вопрос заключается в том, каковы различные реализации интеллектуальных указателей в C ++ и как они сравниваются? Просто простые плюсы и минусы или исключения и подводные камни к тому, что, по вашему мнению, должно работать.
Я опубликовал несколько реализаций, которые я использовал или, по крайней мере, замалчивал и рассматривал возможность использования в качестве ответа ниже, и мое понимание их различий и сходств, которые могут быть не на 100% точными, поэтому не стесняйтесь проверять факты или исправлять меня по мере необходимости.
Цель состоит в том, чтобы узнать о некоторых новых объектах и библиотеках или исправить мое использование и понимание существующих реализаций, которые уже широко используются, и в итоге получить достойную ссылку для других.
источник
osg::ref_ptr
.std::auto_ptr
.std::auto_ptr_ref
это деталь дизайнаstd::auto_ptr
.std::auto_ptr
не имеет ничего общего со сборкой мусора, его основная цель - разрешить безопасную передачу прав собственности в исключительных случаях, особенно в ситуациях вызова и возврата функций.std::unique_ptr
может решить только упомянутые вами «проблемы» со стандартными контейнерами, потому что C ++ изменился, чтобы позволить различать перемещение и копирование, а стандартные контейнеры были изменены, чтобы воспользоваться этим.auto_ptr_ref
деталей реализации). Тем не менее, я согласен с тем, что вы должны опубликовать это как ответ и переформулировать вопрос, чтобы он стал актуальным. Это может затем послужить ссылкой на будущее.Ответы:
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
с ними. Однако в качестве альтернативы вы можете использовать aboost::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
для чего.источник
intrusive_ptr
как правило, более эффективен, чемshared_ptr
, поскольку он позволяет вам выделять одну кучу для каждого объекта.shared_ptr
в общем случае выделяет отдельный небольшой объект кучи для счетчиков ссылок.std::make_shared
можно использовать, чтобы получить лучшее из обоих миров.shared_ptr
с одним выделением кучи.shared_ptr
s? (Не считая разрешения циклических ссылок)Также существует Loki, который реализует интеллектуальные указатели на основе политик.
Другие ссылки на интеллектуальные указатели на основе политик, посвященные проблеме плохой поддержки оптимизации пустой базы наряду с множественным наследованием многими компиляторами:
источник
Помимо приведенных, есть еще несколько ориентированных на безопасность:
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, не неся накладных расходов в режиме выпуска.Вот статья, в которой рассказывается, зачем и как их использовать. (Заметьте, бессовестная вилка.)
источник