Какие реализации C ++ Smart Pointer доступны? - PullRequest
121 голосов
/ 17 февраля 2011

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

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

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

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

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

Ответы [ 3 ]

228 голосов
/ 17 февраля 2011

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. Это ссылка на объект, принадлежащий 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 с ними. Однако вы можете альтернативно использовать 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(), вы можете обнаружить, что ваш объект уничтожен сразу после того, как вы пройдете эту проверку, особенно в многопоточных средах. , Я считаю, что люди считают это устаревшим.

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 для.

5 голосов
/ 03 сентября 2011

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

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

1 голос
/ 13 февраля 2016

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

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 имеет то же поведение, что и необработанный указатель при указании на допустимые объекты, его можно «отключить» (автоматически заменить соответствующим необработанным указателем) с помощью директивы времени компиляции, что позволяет использовать его для помощи в обнаружении ошибок вРежимы отладки / тестирования / бета-версии без дополнительных затрат в режиме выпуска.

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

...