Какие возможности С++ Smart Pointer доступны?

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

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

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

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

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

Ответ 1

С++ 03

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

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

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


С++ 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. Это ссылка на объект, принадлежащий std::shared_ptr, и поэтому не будет препятствовать удалению объекта, если счетчик ссылок std::shared_ptr падает до нуля. Чтобы получить доступ к необработанному указателю, вам сначала нужно получить доступ к std::shared_ptr, вызвав lock, который вернет пустой std::shared_ptr, если уже утерянный указатель истек. Это в первую очередь полезно, чтобы избежать неопределенного подсчета ссылок при использовании нескольких интеллектуальных указателей.


подталкивания

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

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 с ними. Однако вы можете использовать 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 для.

Ответ 2

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

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

Ответ 3

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

SaferCPlusPlus

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

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

mse::TScopeFixedPointer указывает объекты, которые выделены в стеке, или чей "владеющий" указатель выделяется в стеке. Это (намеренно) ограничено в нем функциональностью для повышения безопасности во время компиляции без затрат времени исполнения. Точка указателей "scope" по существу состоит в том, чтобы идентифицировать множество обстоятельств, которые являются простыми и детерминированными настолько, что необходимы механизмы безопасности (runtime).

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

Здесь - статья, описывающая, почему и как их использовать. (Обратите внимание, бесстыдный штепсель.)