Если у меня есть функция, которая должна работать с a shared_ptr
, не было бы более эффективным передать ей ссылку на нее (чтобы избежать копирования shared_ptr
объекта)? Каковы возможные побочные эффекты? Я предвижу два возможных случая:
1) внутри функции делается копия аргумента, как в
ClassA::take_copy_of_sp(boost::shared_ptr<foo> &sp)
{
...
m_sp_member=sp; //This will copy the object, incrementing refcount
...
}
2) внутри функции используется только аргумент, как в
Class::only_work_with_sp(boost::shared_ptr<foo> &sp) //Again, no copy here
{
...
sp->do_something();
...
}
Я не вижу в обоих случаях веской причины передавать boost::shared_ptr<foo>
по значению, а не по ссылке. Передача по значению только "временно" увеличит счетчик ссылок из-за копирования, а затем уменьшит его при выходе из области действия функции. Я что-то не замечаю?
Чтобы уточнить, после прочтения нескольких ответов: я полностью согласен с проблемами преждевременной оптимизации, и я всегда стараюсь сначала профилировать, а затем работать над горячими точками. Мой вопрос был больше с чисто технической точки зрения, если вы понимаете, о чем я.
источник
Ответы:
Смысл отдельного
shared_ptr
экземпляра состоит в том, чтобы гарантировать (насколько это возможно), что пока онshared_ptr
находится в области видимости, объект, на который он указывает, будет существовать, поскольку его счетчик ссылок будет не меньше 1.Таким образом, используя ссылку на a
shared_ptr
, вы отключаете эту гарантию. Итак, во втором случае:Как узнать, что
sp->do_something()
не взорвется из-за нулевого указателя?Все зависит от того, что находится в этих разделах кода «...». Что, если вы вызовете что-то во время первого "...", которое имеет побочный эффект (где-то в другой части кода) очистки
shared_ptr
этого же объекта? А что, если это единственное, что остается отличнымshared_ptr
от этого объекта? До свидания, объект, именно там, где вы собираетесь его использовать.Итак, есть два способа ответить на этот вопрос:
Очень внимательно изучите источник всей вашей программы, пока не убедитесь, что объект не умрет во время выполнения тела функции.
Измените параметр обратно на отдельный объект, а не на ссылку.
Общий совет, который здесь применим: не утруждайтесь внесением рискованных изменений в свой код ради производительности, пока вы не рассчитали время своего продукта в реалистичной ситуации в профилировщике и окончательно не измерили, что изменение, которое вы хотите внести, приведет к значительная разница в производительности.
Обновление для комментатора JQ
Вот надуманный пример. Это заведомо просто, поэтому ошибка будет очевидна. На реальных примерах ошибка не так очевидна, потому что она скрыта слоями реальных деталей.
У нас есть функция, которая куда-то отправит сообщение. Это может быть большое сообщение, поэтому вместо того, чтобы использовать a,
std::string
который, вероятно, копируется, когда он передается в несколько мест, мы используем ashared_ptr
для строки:(Мы просто «отправляем» его в консоль для этого примера).
Теперь мы хотим добавить возможность запоминать предыдущее сообщение. Нам нужно следующее поведение: должна существовать переменная, содержащая самое последнее отправленное сообщение, но пока сообщение отправляется, не должно быть предыдущего сообщения (переменная должна быть сброшена перед отправкой). Итак, мы объявляем новую переменную:
Затем мы изменяем нашу функцию в соответствии с указанными нами правилами:
Итак, перед тем, как мы начнем отправлять, мы отбрасываем текущее предыдущее сообщение, а затем после завершения отправки мы можем сохранить новое предыдущее сообщение. Все хорошо. Вот тестовый код:
И, как и ожидалось, это печатается
Hi!
дважды.Сейчас идет вдоль Г - н Сопровождающий, который смотрит на код и думает: Эй, что параметр
send_message
являетсяshared_ptr
:Очевидно, это можно изменить на:
Подумайте об увеличении производительности, которое это принесет! (Неважно, что мы собираемся отправить обычно большое сообщение по какому-либо каналу, поэтому повышение производительности будет настолько незначительным, что его невозможно будет измерить).
Но настоящая проблема в том, что теперь тестовый код будет демонстрировать неопределенное поведение (в отладочных сборках Visual C ++ 2010 он дает сбой).
Мистер Сопровождающий удивлен этим, но добавляет к нему защитную проверку
send_message
, пытаясь предотвратить возникновение проблемы:Но, конечно, он все еще продолжается и вылетает, потому что
msg
никогда не имеет значения null приsend_message
вызове.Как я уже сказал, когда весь код так близко расположен в тривиальном примере, легко найти ошибку. Но в реальных программах с более сложными отношениями между изменяемыми объектами, которые содержат указатели друг на друга, легко допустить ошибку и сложно построить необходимые тестовые примеры для обнаружения ошибки.
Простое решение, когда вы хотите, чтобы функция могла полагаться на
shared_ptr
постоянное значение, отличное от нуля, заключается в том, чтобы функция выделяла собственное значение trueshared_ptr
, а не полагалась на ссылку на существующийshared_ptr
.Обратной стороной является то, что скопированный a
shared_ptr
не является бесплатным: даже «безблокировочные» реализации должны использовать заблокированную операцию для соблюдения гарантий многопоточности. Таким образом, могут возникнуть ситуации, когда программу можно значительно ускорить, заменив ashared_ptr
наshared_ptr &
. Но это изменение не может быть безопасно внесено во все программы. Это меняет логический смысл программы.Обратите внимание, что аналогичная ошибка возникла бы, если бы мы использовали
std::string
повсюду вместоstd::shared_ptr<std::string>
и вместо:чтобы очистить сообщение, мы сказали:
Тогда симптомом будет случайная отправка пустого сообщения вместо неопределенного поведения. Стоимость дополнительной копии очень большой строки может быть намного более значительной, чем стоимость копирования
shared_ptr
, поэтому компромисс может быть другим.источник
Я обнаружил, что не согласен с ответом, получившим наибольшее количество голосов, поэтому я пошел искать мнения экспертов, и вот они. Из http://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2011-Scott-Andrei-and-Herb-Ask-Us-Anything
Херб Саттер: «когда вы передаете shared_ptrs, копии стоят дорого»
Скотт Мейерс: «В shared_ptr нет ничего особенного, когда дело доходит до того, передаете ли вы его по значению или по ссылке. Используйте точно такой же анализ, который вы используете для любого другого определяемого пользователем типа. Люди, похоже, считают, что shared_ptr каким-то образом решает все проблемы управления, и что, поскольку он небольшой, его обязательно недорого передать по значению. Его нужно скопировать, и с этим связаны затраты ... дорого передать его по значению, поэтому, если я могу уйти это с правильной семантикой в моей программе, я передам его по ссылке на const или reference "
Херб Саттер: «всегда передавайте их по ссылке на const, и очень иногда, может быть, потому что вы знаете, что то, что вы вызываете, может изменить то, что у вас есть ссылка, может быть, тогда вы можете передать их по значению ... если вы скопируете их как параметры, о Боже мой, вам почти никогда не нужно увеличивать этот счетчик ссылок, потому что он все равно остается живым, и вы должны передавать его по ссылке, поэтому, пожалуйста, сделайте это "
Обновление: Херб расширил это здесь: http://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/ , хотя мораль этой истории заключается в том, что вы не должны проходить мимо shared_ptrs вообще «если вы не хотите использовать или манипулировать самим интеллектуальным указателем, например, для совместного использования или передачи права собственности».
источник
shared_ptr
s, не было согласия по вопросу передачи по значению по сравнению с эталоном.const &
повлияет ли изменение параметров на семантику, легко лишь в очень простые программы.Я бы посоветовал не использовать эту практику, если вы и другие программисты, с которыми вы работаете, действительно, действительно не знаете, что делаете.
Во-первых, вы не представляете, как может развиваться интерфейс вашего класса, и хотите, чтобы другие программисты не делали плохих вещей. Передача shared_ptr по ссылке - это не то, чего программист должен ожидать, потому что это не идиоматично, и это упрощает его неправильное использование. Программируйте защитно: сделайте интерфейс трудным для неправильного использования. Передача по ссылке лишь вызовет проблемы позже.
Во-вторых, не оптимизируйте, пока не узнаете, что именно этот класс будет проблемой. Сначала профилируйте, а затем, если ваша программа действительно нуждается в усилении, передаваемом по ссылке, то, возможно,. В противном случае не беспокойтесь о мелочах (например, о дополнительных N инструкциях, необходимых для передачи по значению), вместо этого беспокойтесь о дизайне, структурах данных, алгоритмах и долгосрочной ремонтопригодности.
источник
Да, взять ссылку там нормально. Вы не собираетесь передавать методу совместное владение; он только хочет с этим работать. Вы можете взять ссылку и для первого случая, так как вы все равно ее скопируете. Но в первом случае он берет на себя ответственность. Есть способ скопировать его только один раз:
Вы также должны скопировать его при возврате (т.е. не возвращать ссылку). Потому что ваш класс не знает, что с ним делает клиент (он может сохранить указатель на него, и тогда произойдет большой взрыв). Если позже выяснится, что это узкое место (первый профиль!), Вы все равно можете вернуть ссылку.
Изменить : Конечно, как указывают другие, это верно только в том случае, если вы знаете свой код и знаете, что вы каким-то образом не сбрасываете переданный общий указатель. Если сомневаетесь, просто пройдите по стоимости.
источник
Разумно пройти
shared_ptr
мимоconst&
. Это вряд ли вызовет проблемы (за исключением маловероятного случаяshared_ptr
, когда указанная ссылка будет удалена во время вызова функции, как подробно описано Earwicker), и, вероятно, будет быстрее, если вы передадите много из них. Помните; по умолчаниюboost::shared_ptr
потокобезопасный, поэтому его копирование включает потокобезопасное приращение.Попробуйте использовать,
const&
а не просто&
, потому что временные объекты не могут передаваться по неконстантной ссылке. (Хотя языковое расширение в MSVC все равно позволяет это делать)источник
Во втором случае сделать это проще:
Вы можете назвать это как
источник
Я бы избегал "простой" ссылки, если только функция явно не может изменять указатель.
A
const &
может быть разумной микрооптимизацией при вызове небольших функций - например, для включения дальнейших оптимизаций, таких как встраивание некоторых условий. Кроме того, инкремент / декремент - поскольку он потокобезопасен - является точкой синхронизации. Однако я не ожидал, что это будет иметь большое значение в большинстве сценариев.Как правило, вам следует использовать более простой стиль, если у вас нет причин не делать этого. Затем либо используйте его
const &
последовательно, либо добавьте комментарий, почему, если вы используете его только в нескольких местах.источник
Я бы поддержал передачу общего указателя по константной ссылке - семантика, согласно которой переданная с указателем функция НЕ владеет указателем, что является чистой идиомой для разработчиков.
Единственная ошибка заключается в том, что в многопоточных программах объект, на который указывает общий указатель, уничтожается в другом потоке. Таким образом, можно с уверенностью сказать, что использование константной ссылки общего указателя безопасно в однопоточной программе.
Передача общего указателя по неконстантной ссылке иногда опасна - причина в функциях подкачки и сброса, которые функция может вызывать внутри, чтобы уничтожить объект, который все еще считается действительным после возврата из функции.
Думаю, дело не в преждевременной оптимизации - дело в том, чтобы избежать ненужной траты циклов ЦП, когда вы четко понимаете, что хотите делать, и идиома кодирования твердо принята вашими коллегами-разработчиками.
Только мои 2 цента :-)
источник
Кажется, что все плюсы и минусы здесь могут быть обобщены на ЛЮБОЙ тип, передаваемый по ссылке, а не только на shared_ptr. На мой взгляд, вы должны знать семантику передачи по ссылке, константной ссылке и значению и правильно ее использовать. Но нет ничего плохого в передаче shared_ptr по ссылке, если только вы не думаете, что все ссылки плохие ...
Чтобы вернуться к примеру:
Как узнать, что
sp.do_something()
не взорвется из-за болтающейся стрелки?Правда в том, что, shared_ptr или нет, const или нет, это может произойти, если у вас есть недостаток дизайна, например, прямое или косвенное разделение прав собственности
sp
между потоками, неправильное использование объектаdelete this
, который имеет, у вас циклическое владение или другие ошибки владения.источник
Одна вещь, о которой я еще не упоминал, заключается в том, что когда вы передаете общие указатели по ссылке, вы теряете неявное преобразование, которое вы получаете, если хотите передать общий указатель производного класса через ссылку на общий указатель базового класса.
Например, этот код выдаст ошибку, но он будет работать, если вы измените
test()
так, чтобы общий указатель не передавался по ссылке.источник
Я предполагаю, что вы знакомы с преждевременной оптимизацией и спрашиваете об этом либо в академических целях, либо потому, что вы изолировали уже существующий код, который недостаточно эффективен.
Переход по ссылке разрешен
Передача по константной ссылке лучше и обычно может использоваться, поскольку она не заставляет константность на указанном объекте.
Вы не рискуете потерять указатель из-за использования ссылки. Эта ссылка свидетельствует о том, что у вас есть копия интеллектуального указателя ранее в стеке, и только один поток владеет стеком вызовов, поэтому уже существующая копия никуда не денется.
Использование ссылок часто более эффективно по указанным вами причинам, но не гарантируется. . Помните, что разыменование объекта тоже может потребовать работы. Ваш идеальный сценарий использования ссылок будет, если ваш стиль кодирования включает в себя множество небольших функций, где указатель будет передаваться от функции к функции до функции.
Вы всегда должны избегать хранения своего умного указателя в качестве справочника. Ваш
Class::take_copy_of_sp(&sp)
пример показывает правильное использование для этого.источник
Предполагая, что нас не интересует правильность констант (или более того, вы имеете в виду, чтобы позволить функциям иметь возможность изменять или разделять владение передаваемыми данными), передача boost :: shared_ptr по значению безопаснее, чем передача его по ссылке как мы позволяем исходному boost :: shared_ptr управлять своим временем жизни. Рассмотрим результаты следующего кода ...
Из приведенного выше примера мы видим, что передача sharedA по ссылке позволяет FooTakesReference сбрасывать исходный указатель, что уменьшает его счетчик использования до 0, уничтожая его данные. Однако FooTakesValue не может сбросить исходный указатель, что гарантирует возможность использования данных sharedB . Когда неизбежно приходит другой разработчик и пытается воспользоваться хрупким существованием sharedA , наступает хаос. Однако удачливый разработчик sharedB уходит домой пораньше, поскольку в его мире все в порядке.
Безопасность кода в этом случае намного перевешивает любое улучшение скорости копирования. В то же время boost :: shared_ptr предназначен для повышения безопасности кода. Будет намного проще перейти от копии к ссылке, если что-то требует такой оптимизации ниши.
источник
Сэнди пишет: «Кажется, что все плюсы и минусы здесь могут быть обобщены на ЛЮБОЙ тип, передаваемый по ссылке, а не только на shared_ptr».
В некоторой степени это правда, но смысл использования shared_ptr состоит в том, чтобы устранить проблемы, касающиеся времени жизни объектов, и позволить компилятору сделать это за вас. Если вы собираетесь передать общий указатель по ссылке и разрешить клиентам вашего объекта с подсчетом ссылок вызывать неконстантные методы, которые могут освободить данные объекта, то использование общего указателя почти бессмысленно.
Я написал «почти» в предыдущем предложении, потому что производительность может быть проблемой, и это «может» быть оправдано в редких случаях, но я бы также сам избегал этого сценария и сам искал все возможные другие решения по оптимизации, например, чтобы серьезно посмотреть при добавлении еще одного уровня косвенности, ленивых вычислений и т. д.
Код, который существует после его автора или даже публикует его в памяти автора, который требует неявных предположений о поведении, в частности о поведении относительно времени жизни объекта, требует четкой, краткой, удобочитаемой документации, и тогда многие клиенты все равно не будут ее читать! Простота почти всегда важнее эффективности, и почти всегда есть другие способы быть эффективными. Если вам действительно нужно передавать значения по ссылке, чтобы избежать глубокого копирования конструкторами копирования ваших объектов с подсчетом ссылок (и оператора равенства), то, возможно, вам следует подумать о том, как сделать глубоко скопированные данные указателями с подсчетом ссылок, которые могут быть скопировал быстро. (Конечно, это всего лишь один проектный сценарий, который может не подходить к вашей ситуации).
источник
Раньше я работал в проекте, в котором был очень строгий принцип передачи интеллектуальных указателей по значению. Когда меня попросили провести некоторый анализ производительности, я обнаружил, что на увеличение и уменьшение счетчиков ссылок интеллектуальных указателей приложение тратит от 4 до 6% используемого процессорного времени.
Если вы хотите передавать интеллектуальные указатели по значению, чтобы избежать проблем в странных случаях, как описано Дэниелом Эрвикером, убедитесь, что вы понимаете цену, которую вы платите за это.
Если вы решите использовать ссылку, основная причина использования константной ссылки состоит в том, чтобы сделать возможным неявное восходящее преобразование, когда вам нужно передать общий указатель на объект из класса, который наследует класс, который вы используете в интерфейсе.
источник
В дополнение к тому, что сказал litb, я хотел бы указать, что во втором примере он, вероятно, будет передаваться по ссылке const, чтобы вы были уверены, что случайно не измените его.
источник
пройти по значению:
пройти по ссылке:
пройти по указателю:
источник
Каждый фрагмент кода должен иметь какой-то смысл. Если вы передадите общий указатель по значению повсюду в приложении, это означает: «Я не уверен в том, что происходит в другом месте, поэтому я предпочитаю чистую безопасность ». Это не то, что я называю хорошим знаком доверия для других программистов, которые могут ознакомиться с кодом.
В любом случае, даже если функция получает ссылку на константу, а вы «не уверены», вы все равно можете создать копию общего указателя в заголовке функции, чтобы добавить сильную ссылку на указатель. Это также можно рассматривать как намек на дизайн («указатель может быть изменен в другом месте»).
Так что да, ИМО, по умолчанию должно быть « передать по константной ссылке ».
источник