Подтвердить что ты не робот

Передача const shared_ptr <T> и по сравнению с shared_ptr <T> в качестве параметра

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

void doSomething(std::shared_ptr<T> o) {}

против

void doSomething(const std::shared_ptr<T> &o) {}

Однако, разве второй вариант фактически не побеждает цель общего указателя? Мы на самом деле разделяем общий указатель здесь, поэтому, если по каким-то причинам указатель освобождается в вызывающем коде (подумайте о повторном включении или побочных эффектах), указатель const станет недействительным. Ситуация, которую должен предотвратить общий указатель. Я понимаю, что const & экономит некоторое время, так как нет копирования и блокировки для управления счетом. Но цена делает код менее безопасным, верно?

4b9b3361

Ответ 1

Преимущество прохождения shared_ptr на const& заключается в том, что счетчик ссылок не нужно увеличивать, а затем уменьшать. Поскольку эти операции должны быть потокобезопасными, они могут быть дорогими.

Вы совершенно правы, что существует риск того, что у вас может быть цепочка проходов по ссылке, которая позже лишает права главой цепочки. Это случилось со мной однажды в реальном проекте с реальными последствиями. Одна функция обнаружила shared_ptr в контейнере и передала ссылку на нее по стеку вызовов. Функция, находящаяся в глубине стека вызовов, удаляла объект из контейнера, в результате чего все ссылки внезапно ссылались на объект, который больше не существовал.

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

(Я предполагаю, что у вас есть прецедент, где есть какая-то конкретная причина для передачи по shared_ptr, а не по ссылке. Наиболее распространенной причиной является то, что вызываемой функции может потребоваться продлить срок службы объекта. )

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

Этот конкретный тип объекта может жить в двух контейнерах. Один, когда он был активным, и один, когда он был неактивным. Некоторые операции работали над активными объектами, некоторые - с неактивными объектами. Случай с ошибкой произошел, когда команда была получена от неактивного объекта, который сделал его активным, в то время как только shared_ptr к объекту удерживался контейнером неактивных объектов.

Неактивный объект находился в контейнере. Ссылка на shared_ptr в контейнере передавалась по ссылке на обработчик команд. Через цепочку ссылок этот shared_ptr в конечном итоге попал в код, который понял, что это неактивный объект, который должен был быть активным. Объект был удален из неактивного контейнера (который уничтожил неактивный контейнер shared_ptr) и добавлен в активный контейнер (который добавил другую ссылку на shared_ptr, переданную в процедуру "добавить" ).

В этот момент было возможно, что единственным shared_ptr для объекта, который существовал, был тот, который был в неактивном контейнере. Каждая другая функция в стеке вызовов просто ссылалась на нее. Когда объект был удален из неактивного контейнера, объект мог быть уничтожен, и все эти ссылки были в shared_ptr, которые больше не существовали.

Потребовалось около месяца, чтобы распутать это.

Ответ 2

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

Но когда вы передаете shared_ptr, то предпочтительно передайте его по ссылке const, так как копирование shared_ptr имеет дополнительные накладные расходы. Копирование должно обновить общий счетчик ссылок, и это обновление должно быть потокобезопасным. Следовательно, существует небольшая неэффективность, которую можно (безопасно) избегать.

Относительно

" цена делает код менее безопасным, не так ли?

Нет. Цена является дополнительным указателем на наивно сгенерированный машинный код, но компилятор справляется с этим. Итак, все, что нужно, просто избегать незначительных, но совершенно ненужных накладных расходов, которые компилятор не может оптимизировать для вас, если он не супер-умный.

Как объяснил Дэвид Шварц в своем ответе, когда вы передаете ссылку на const проблему <сглаживания , где вызываемая вами функция в свою очередь изменяет или вызывает функцию, которая изменяет исходный объект, возможное. А по закону Мерфи это произойдет в самое неудобное время, с максимальной стоимостью, и с самым запутанным непроницаемым кодом. Но это так, независимо от того, является ли аргумент string или shared_ptr или что-то еще. К счастью, это очень редкая проблема. Но имейте это в виду, также для передачи shared_ptr экземпляров.

Ответ 3

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

Сравнение влияния производительности на них не имеет значения, если они семантически отличаются.

из https://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/

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

и на этот раз я полностью согласен с Herb:)

И еще одна цитата из того же самого, которая напрямую отвечает на вопрос

Рекомендация: используйте не-const shared_ptr & параметр только для изменения shared_ptr. Используйте константу shared shared_ptr & как параметр только в том случае, если вы не уверены, возьмете ли вы копию и долей собственности; в противном случае используйте * вместо (или, если не nullable, a &)

Ответ 4

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

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

Ответ 5

Если в вашем методе не применяется модификация владения, нет никакой пользы для вашего метода взять shared_ptr путем копирования или ссылки на const, он загрязняет API и потенциально может нанести накладные расходы (при передаче по копии)

Чистым способом является передача базового типа с помощью ref ref или ref в зависимости от вашего использования.

void doSomething(const T& o) {}

auto s = std::make_shared<T>(...);
// ...
doSomething(*s);

Основной указатель не может быть освобожден во время вызова метода

Ответ 6

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

Итак, вы правы, std::shared_ptr<T> const& может использоваться как улучшение производительности только за std::shared_ptr<T>.


Вы также правы, что существует теоретический риск того, что указатель/ссылка станет болтаться из-за некоторого сглаживания.

При этом риск скрыт в любой программе на С++: любое использование указателя или ссылки является риском. Я бы сказал, что несколько случаев появления std::shared_ptr<T> const& должны быть каплей воды по сравнению с общим числом применений T&, T const&, T*,...


Наконец, я хотел бы указать, что прохождение a shared_ptr<T> const& является странным. Возможны следующие случаи:

  • shared_ptr<T>: Мне нужна копия shared_ptr
  • T*/T const&/T&/T const&: Мне нужен (возможно, нулевой) дескриптор для T

Следующий случай гораздо реже:

  • shared_ptr<T>&: я могу перезагрузить shared_ptr

Но прохождение shared_ptr<T> const&? Легитимное использование очень редко.

Передача shared_ptr<T> const&, где все, что вы хотите, является ссылкой на T, является анти-шаблоном: вы вынуждаете пользователя использовать shared_ptr, когда они могут выделять T другим способом! В большинстве случаев (99,99..%) вам не важно, как выделяется T.

Единственный случай, когда вы передадите shared_ptr<T> const&, - это если вы не уверены, нужна ли вам копия или нет, а потому, что вы профилировали программу и показали, что этот атомный прирост/декремент был узким местом, вы решили отложить создание копии только в тех случаях, когда это необходимо.

Это такой крайний случай, что любое использование shared_ptr<T> const& следует рассматривать с наивысшей степенью подозрений.