C ++ - передача ссылок на std :: shared_ptr или boost :: shared_ptr

Если у меня есть функция, которая должна работать с 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> по значению, а не по ссылке. Передача по значению только «временно» увеличит счетчик ссылок из-за копирования, а затем уменьшит его при выходе из области действия функции. Я что-то не замечаю?

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


person abigagli    schedule 29.11.2008    source источник
comment
Я не знаю, можете ли вы изменить теги своего вопроса, но попробуйте добавить туда тег повышения. Я попытался найти этот вопрос, но не смог его найти, потому что искал теги boost и smart-pointer. Итак, я нашел ваш вопрос сразу после того, как написал свой вопрос   -  person Edison Gustavo Muenz    schedule 06.03.2009


Ответы (17)


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

Class::only_work_with_sp(boost::shared_ptr<foo> sp)
{
    // sp points to an object that cannot be destroyed during this function
}

Таким образом, используя ссылку на shared_ptr, вы отключаете эту гарантию. Итак, во втором случае:

Class::only_work_with_sp(boost::shared_ptr<foo> &sp) //Again, no copy here  
{    
    ...  
    sp->do_something();  
    ...  
}

Откуда вы знаете, что sp->do_something() не взорвется из-за нулевого указателя?

Все зависит от того, что находится в этих разделах кода «...». Что, если вы вызовете что-то во время первого "...", которое имеет побочный эффект (где-то в другой части кода) очистки shared_ptr для того же объекта? А что, если он окажется единственным оставшимся shared_ptr отличным от этого объекта? До свидания, объект, именно там, где вы собираетесь его использовать.

Итак, есть два способа ответить на этот вопрос:

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

  2. Измените параметр обратно на отдельный объект, а не на ссылку.

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

Обновление для JQ комментатора

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

У нас есть функция, которая куда-то отправит сообщение. Это может быть большое сообщение, поэтому вместо использования std::string, который, вероятно, копируется при передаче в несколько мест, мы используем shared_ptr в строке:

void send_message(std::shared_ptr<std::string> msg)
{
    std::cout << (*msg.get()) << std::endl;
}

(Мы просто «отправляем» его на консоль для этого примера).

Теперь мы хотим добавить возможность запоминать предыдущее сообщение. Нам нужно следующее поведение: должна существовать переменная, содержащая самое последнее отправленное сообщение, но пока сообщение отправляется, не должно быть предыдущего сообщения (переменная должна быть сброшена перед отправкой). Итак, мы объявляем новую переменную:

std::shared_ptr<std::string> previous_message;

Затем мы изменяем нашу функцию в соответствии с указанными нами правилами:

void send_message(std::shared_ptr<std::string> msg)
{
    previous_message = 0;
    std::cout << *msg << std::endl;
    previous_message = msg;
}

Итак, перед тем, как начать отправку, мы отбрасываем текущее предыдущее сообщение, а затем, после завершения отправки, мы можем сохранить новое предыдущее сообщение. Все хорошо. Вот тестовый код:

send_message(std::shared_ptr<std::string>(new std::string("Hi")));
send_message(previous_message);

И как и ожидалось, это напечатает Hi! дважды.

Теперь появляется мистер Сопровождающий, который смотрит на код и думает: Эй, этот параметр для send_message - это shared_ptr:

void send_message(std::shared_ptr<std::string> msg)

Очевидно, это можно изменить на:

void send_message(const std::shared_ptr<std::string> &msg)

Подумайте об увеличении производительности, которое это принесет! (Неважно, что мы собираемся отправить обычно большое сообщение по какому-либо каналу, поэтому повышение производительности будет настолько незначительным, что его невозможно будет измерить).

Но настоящая проблема в том, что теперь тестовый код будет демонстрировать неопределенное поведение (в отладочных сборках Visual C ++ 2010 он дает сбой).

Мистер Сопровождающий удивлен этим, но добавляет защитную проверку к send_message в попытке предотвратить возникновение проблемы:

void send_message(const std::shared_ptr<std::string> &msg)
{
    if (msg == 0)
        return;

Но, конечно, он все равно продолжается и вылетает, потому что msg никогда не имеет значения null, когда вызывается send_message.

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

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

Обратной стороной является то, что скопированный shared_ptr не является бесплатным: даже «безблокировочные» реализации должны использовать заблокированную операцию для соблюдения гарантий многопоточности. Таким образом, могут возникнуть ситуации, когда программу можно значительно ускорить, заменив shared_ptr на shared_ptr &. Но это изменение не может быть безопасно внесено во все программы. Это меняет логический смысл программы.

Обратите внимание, что аналогичная ошибка возникла бы, если бы мы использовали std::string вместо std::shared_ptr<std::string>, и вместо:

previous_message = 0;

чтобы очистить сообщение, мы сказали:

previous_message.clear();

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

person Daniel Earwicker    schedule 30.11.2008
comment
Переданный shared_ptr уже находится в области на месте вызова. Возможно, вы сможете создать сложный сценарий, в котором код в этом вопросе взорвется из-за висячего указателя, но тогда, я полагаю, у вас есть более серьезные проблемы, чем параметр ссылки! - person Magnus Hoff; 30.11.2008
comment
Он может храниться в члене. Вы можете вызвать что-нибудь, что происходит, чтобы очистить этот член. Весь смысл smart_ptr состоит в том, чтобы избежать необходимости координировать время жизни в иерархиях или областях, которые гнездятся вокруг стека вызовов, поэтому лучше предположить, что время жизни не делает этого в таких программах. - person Daniel Earwicker; 30.11.2008
comment
Эрвикер: Ваш сценарий, конечно, реален, но мой личный опыт маловероятен. Возможно, мы просто работали в разных кодовых базах. :) Хорошо, что эта страница теперь содержит обе точки обзора;) - person Magnus Hoff; 01.12.2008
comment
Но это не совсем моя точка зрения! Если вы думаете, что то, что я говорю, связано с моим кодом, возможно, вы меня не поняли. Я говорю о неизбежном следствии того, что shared_ptr существует в первую очередь: время жизни многих объектов не просто связано с вызовами функций. - person Daniel Earwicker; 01.12.2008
comment
Магнус Хофф придерживался ПРАВИЛЬНОЙ точки зрения. В первом пункте Эрвикера это смешно, потому что, если вы не можете быть уверены, что объект существует во время тела функции, как вы можете быть уверены, что объект существует, прежде чем он сделает копию для функции? В этом случае общий указатель, который вы получили внутри тела функции, не заслуживает доверия - ничто не заслуживает доверия. - person JQ.; 23.11.2011
comment
@JQ - Я добавил длинное обновление, чтобы, надеюсь, прояснить суть дела. - person Daniel Earwicker; 02.12.2011
comment
Извините, это должен быть @JQ. с точкой. - person Daniel Earwicker; 02.12.2011
comment
Пример все еще немного непонятен. Почему перед вызовом функции необходимо выполнить сброс? Возможно, это предварительное условие для этой функции, что предыдущее значение уничтожается, и в противном случае оно вызывает неопределенное поведение? - person UncleBens; 05.12.2011
comment
@DanielEarwicker Привет, Даниэль, спасибо, что обновили для меня комментарии. Мне потребовалось много времени, чтобы понять около 50% (?) Того, чего вы пытаетесь достичь. Но я нашел этот пример неубедительным. Причина: если пользователь использует send_message () и в то же время может получить доступ к previous_message, он должен знать, что функция делает с переменной. Если он не в курсе, это плохой дизайн самой функции. - person JQ.; 05.12.2011
comment
@UncleBens - возможно, в реальном примере кешированный объект - это не строка, а некоторый внешний ресурс, инкапсулированный в объект, например дескриптор файла или какое-то соединение, управляемое ОС, и мы хотим разрушить старую, прежде чем создавать новую. - person Daniel Earwicker; 05.12.2011
comment
@JQ - добро пожаловать в реальный мир! :) Он полон API, которые делают ненужным использование изменяемых переменных состояния. Функция C strtok - старый неприятный пример. Суть вопроса заключалась в том, каковы возможные плохие побочные эффекты изменения shared_ptr<T> на const shared_ptr<T> &. А в реальном коде, где классы имеют изменяемые члены, и некоторые функции-члены зависят от этих членов, а другие функции-члены предоставляют к ним доступ, и есть сложные сети объектов, которые содержат ссылки друг на друга, тогда для этого есть много возможностей. какая-то проблема. - person Daniel Earwicker; 05.12.2011
comment
Поэтому, если вы передаете переменную-член по ссылке const в функцию, которая изменяет эту переменную-член перед доступом к параметру, ваш параметр будет неожиданно изменен. Это относится ко всему, а не только к shared_ptr. Означает ли это, что практическое правило должно передавать все объекты по значению, а не по константной ссылке, если их копировать дешево? - person Jon; 13.01.2012
comment
@Jon - это правило не может быть правильным, потому что часто вы хотите делиться ссылками на объекты по дизайну, поэтому тот факт, что копирование может быть дешевым, не влияет на ваше решение. Если вам передан const T&, можете ли вы предположить, что его состояние не изменится, пока ваша функция не завершится? Если вы видите, что ваша функция никогда не приводит к изменению any T, то вы в полной безопасности. Если ваша функция напрямую изменяет некоторые T (возможно, вызывая какую-либо другую функцию, которая выполняет модификацию), тогда да, у вас может быть проблема, ожидающая своего появления. (продолжение ...) - person Daniel Earwicker; 13.01.2012
comment
Однако цель shared_ptr<T> v - служить лучшим T *v, и в чем его особенное преимущество? Это предотвращает преждевременное разрушение объекта. Снова измените его на shared_ptr<T> &v, и вы сбросите это преимущество. Весь смысл shared_ptr в том, что его копирование достаточно дешево, чтобы вы могли широко от него зависеть, так что вам не нужно нести ответственность за анализ каждого аспекта жизни вашего объекта. Это для ситуаций, когда вам нужно совместное использование по дизайну и когда у вас есть сложные перекрывающиеся сроки существования объектов, поэтому анализ вручную будет чертовски трудным. - person Daniel Earwicker; 13.01.2012
comment
Все зависит от того, что вы считаете дешевым. Копирование shared_ptr требует модификации счетчика взаимосвязанных ссылок, что для меня относительно дорого. Таким образом, у меня есть правило, согласно которому shared_ptrs всегда передаются по константной ссылке и хранятся в виде копий. Единственный случай, когда это может быть проблематично, - это когда один из членов передается функции, которая сбрасывает член, а затем ожидает, что указатель все еще будет действителен после этого - и этот случай исчезающе маловероятен и его легко исправить, скопировав в локальный перед передачей, которая в любом случае часто требуется для параллелизма. - person Miral; 14.02.2012
comment
@Miral - и этот случай маловероятен - отлично, тогда нет проблем! :) Не уверен, что сам бы присвоил такую ​​вероятность. Также я предпочитаю избегать ошибок сейчас, а не исправлять их позже, поэтому простота исправления - это хорошо, но не так хорошо, как быть правильным в первую очередь. Наконец (как указано выше) я предпочитаю оптимизировать на основе измерений; все, что случается нечасто, стоит дешево, все, что случается много, - дорого. - person Daniel Earwicker; 14.02.2012
comment
Передача параметров обычно происходит довольно часто. Таким образом, вы должны сделать это как можно быстрее. :) Копирование должно производиться только в тех случаях, когда это действительно необходимо, что, в свою очередь, должно быть меньшим числом случаев. Но, как всегда, это зависит от поставленной задачи и того, насколько хорошо вы знаете вызывающего абонента. - person Miral; 15.02.2012
comment
Имейте в виду, это может быть следствием другого правила, которому я стараюсь следовать, которое делает это использование безопасным - shared_ptrs всегда должен возвращаться по значению, а не по ссылке. В приведенном выше примере возникают проблемы, потому что к previous_message осуществляется прямой доступ. Класс, определяющий send_message, должен знать, что это небезопасно из-за конкретной реализации, и воздерживаться. Внешний код не знает реализации, поэтому может не осознавать, что это небезопасно, но они защищены, потому что не могут напрямую получить доступ к previous_message, они могут только вызвать get_previous_message, который по своей сути копирует (возврат по значению). - person Miral; 15.02.2012
comment
@Miral - как я уже сказал, это надуманный пример. Это заведомо просто, поэтому ошибка будет очевидна. На реальных примерах ошибка не так очевидна, потому что она скрыта слоями реальных деталей. Меня больше всего беспокоит то, что люди могут подумать, что они могут войти в какой-то существующий хорошо протестированный производственный код и улучшить его, изменив shared_ptr на const shared_ptr &, ошибочно полагая, что это не изменит семантику. Необходимый анализ всей кодовой базы, необходимый для безопасного внесения такого изменения, может быть чертовски сложным, в зависимости от сложности. - person Daniel Earwicker; 15.02.2012
comment
Верно, но никогда не безопасно вносить такие изменения, не зная, какими будут побочные эффекты. И отчасти для этого нужны модульные тесты. :) Это еще не означает, что значение по умолчанию для нового кода должно передаваться по значению. - person Miral; 17.02.2012
comment
@Miral - Совершенно верно. Одна из возможных интерпретаций этого вопроса: будет ли это изменение иметь какие-либо побочные эффекты? Выбор значения по умолчанию для нового кода - более открытый вопрос. В конце концов, C ++ - это преждевременная оптимизация! :) Одно из моих правил - действовать осторожно, пока вы не будете в состоянии провести реальные измерения того, где необходима оптимизация ... но тогда есть еще одно такое правило: когда вы обнаружите, что вам нужно shared_ptr, вы выходите за пределы Основная цель C ++ и в область истинных языков GC. - person Daniel Earwicker; 17.02.2012
comment
Ошибка во втором примере не имеет ничего общего с shared_ptr. Замените аргумент std::shared_ptr<std::string> чем-нибудь другим - например, std::string - и код по-прежнему ломается, отбрасывая аргумент msg через глобальный previous_message. Единственное отличие состоит в том, что версия std::shared_ptr<std::string> выйдет из строя вместо того, чтобы давать неверный результат, и это потому, что send_message() не гарантирует, что указатель действителен перед его использованием (что необходимо сделать независимо от того, передан ли shared_ptr по ссылке) . - person Josh Townzen; 21.06.2012
comment
@JoshTownzen - см. Последнюю часть ответа, начало. Обратите внимание, что аналогичная ошибка возникла бы, если бы мы использовали std :: string повсюду ... что говорит в точности то же самое. Возможно, вы упустили суть, заключающуюся в том, что программа может быть изначально абсолютно правильной, но затем станет абсолютно неправильной, если вы измените shared_ptr на const shared_ptr &. - person Daniel Earwicker; 21.06.2012
comment
Просто ссылка на другой ответ SO, который соглашается, что можно безопасно передавать ссылку на общий указатель. ССЫЛКА: передача ссылки допустима, сохранение ссылки недопустимо - person Trevor Boyd Smith; 18.10.2012
comment
@TrevorBoydSmith - спасибо, что сообщили мне; Я добавил комментарий к этому ответу, объясняющий, почему это неправильно. - person Daniel Earwicker; 19.10.2012
comment
Этот ответ вообще не имеет смысла. Это применимо и к ситуациям с другими типами переменных, поскольку писатель добавил позже при редактировании. Таким образом, это не отвечает на вопрос и не является правильной точкой зрения. Логика кода, написанного в этом посте, сама по себе ГЛУБОКО ИСПОЛЬЗУЕТСЯ! - person Etherealone; 02.03.2013
comment
@Tolga - поскольку это применимо и к другим ситуациям с другими типами переменных, вы делаете вывод, что это не относится к этой ситуации? Это ошибочная логика. - person Daniel Earwicker; 02.03.2013
comment
@DanielEarwicker полностью согласен со всеми вашими утверждениями и удивлен уровнем противодействия. Что-то, что делает ваши опасения еще более актуальными, - это многопоточность, когда это вовлекается, гарантии достоверности объектов становятся гораздо более важными. Хороший ответ. - person radman; 09.03.2013
comment
Не так давно я обнаружил очень серьезную ошибку, связанную с передачей ссылки на общий указатель. Код обрабатывал изменение состояния объекта, и когда он заметил, что состояние объекта изменилось, он удалил его из коллекции объектов в предыдущем состоянии и переместил в коллекцию объектов в новом состоянии. Операция удаления уничтожила последний общий указатель на объект. Функция-член была вызвана по ссылке на общий указатель в коллекции. Бум. Дэниел Эрвикер прав. - person David Schwartz; 11.03.2013
comment
Противодействие возникает из-за того, что люди ожидают, что в общем случае общий указатель будет передаваться из локальной области действия вызывающей функции, и действительно, часто безопасно передавать по ссылке. Я думаю, что то, что Дэниел пытается сказать (что люди часто упускают), - это риск того, что эти предположения будут нарушены людьми, которые позже будут поддерживать код. Если вы можете с уверенностью предположить, что каждый, кто когда-либо взглянет на код, является безупречным экспертом, тогда вы можете делать более или менее то, что вам нравится, но годы программирования в коммерческих средах имеют тенденцию опровергать подобные предположения у большинства из нас. (^_^) - person Cartroo; 03.01.2014
comment
@Cartroo - Я подозреваю, что большинство людей советуют читателям такие же, как Саттер, но не очень осторожно. Краткая версия его совета Prefer passing objects by value, *, or &, not by smart pointer. Но он добавляет предупреждение. Обратите внимание, это предполагает, что указатель не имеет псевдонима. Вы должны быть осторожны, если параметр умного указателя может иметь псевдоним, но в этом отношении он ничем не отличается от любого другого объекта с псевдонимом. Это предупреждение - в точности мой ответ! Я просто подробно расскажу, что означает псевдоним и почему он вызывает проблемы [продолжение ...] - person Daniel Earwicker; 06.01.2014
comment
[продолжение ...] Но как только вы это поймете, тогда встанет вопрос, хотите ли вы рискнуть позже обнаружить восхитительно забавные и тонкие ошибки псевдонима в обмен на быстрый код, или вы предпочтете более строгие гарантии того, что правильность, так что вы можете затем измерить и инвестировать в усилия по оптимизации (со всей осторожностью, чтобы избежать ошибок псевдонима, которые это влечет за собой) там, где это действительно необходимо. И я настоятельно рекомендую последнее. - person Daniel Earwicker; 06.01.2014
comment
Если мы будем слишком осторожны, мы получим невероятно медленный код. - person Mike Weir; 22.10.2016
comment
@MikeWeir да, на C ++. Современные языки с идеальным уплотнением GC могут использовать внутри себя чистые указатели с абсолютной безопасностью и выделять / освобождать краткосрочные объекты со скоростью, сравнимой с распределением стека. Ирония заключается в том, что C ++, пытаясь быть минимальным и эффективным, переносит эту проблему в область пользователя, и в результате пользователи вынуждены выбирать между медленным и безопасным, а также быстрым и небезопасным. Поэтому это спорный вопрос. Если вы в основном моделируете отношения между динамически выделяемыми объектами, C ++ не является оптимальным. Это ситуация для большинства приложений. - person Daniel Earwicker; 22.10.2016
comment
Разве утверждение "неопределенное поведение" const std::shared_ptr<std::string> &msg ложно из-за этого? Согласно этой статье, постоянные ссылки на локальные переменные прекрасно подходят для C ++. Поэтому, когда VS 2010 выходит из строя, это на самом деле будет ошибкой компилятора. - person xamid; 11.12.2020
comment
@xamid - как говорится на этой странице, обратите внимание, это относится только к ссылкам на основе стека. Это не работает для ссылок, которые являются членами объектов. в приведенном выше примере previous_message - это не локальная переменная, а некоторое долгоживущее состояние, которое сохраняется между вызовами send_message. По замыслу, shared_ptr переходит в nullptr, когда больше нет реальных экземпляров shard_ptr, указывающих на тот же объект. & -Ref на shared_ptr не является экземпляром и не поддерживает активность указанного объекта. Изменение shared_ptr на const shared_ptr& говорит: «Ничего страшного, если оно станет нулевым». - person Daniel Earwicker; 11.12.2020

Я обнаружил, что не согласен с ответом, получившим наибольшее количество голосов, поэтому я пошел искать мнения экспертов, и вот они. Из http://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2011-Scott-Andrei-and-Herb-Ask-Us-Anything

Herb Sutter: когда вы передаете shared_ptrs, копии стоят дорого

Скотт Мейерс: В shared_ptr нет ничего особенного, когда дело касается того, передаете ли вы его по значению или по ссылке. Используйте точно такой же анализ, который вы используете для любого другого пользовательского типа. Похоже, у людей сложилось мнение, что shared_ptr каким-то образом решает все проблемы управления, и что, поскольку он маленький, его обязательно недорого передавать по значению. Его нужно скопировать, и с этим связаны затраты ... передавать его по значению дорого, поэтому, если я смогу уйти с этим с правильной семантикой в ​​моей программе, я передам его по ссылке на const или вместо ссылки

Херб Саттер: всегда передавайте их по ссылке на const, и очень редко, может быть, потому что вы знаете, что то, что вы вызываете, может изменить то, что вы получили ссылку, может быть, тогда вы можете передать их по значению ... если вы скопируете их как параметры, о мой Боже, вам почти никогда не нужно увеличивать этот счетчик ссылок, потому что он все равно остается живым, и вы должны передавать его по ссылке, поэтому, пожалуйста, сделайте это

Обновление: Herb расширил это здесь: http://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/, хотя мораль этой истории заключается в том, что вам вообще не следует передавать shared_ptrs, если вы не хотите использовать или управлять самим интеллектуальным указателем, например, для совместного использования или передачи права собственности.

person Jon    schedule 13.01.2012
comment
Хорошая находка! Приятно видеть, как два ведущих эксперта в этой области публично опровергают общепринятое мнение о SO. - person Stephan Tolksdorf; 01.02.2012
comment
В shared_ptr нет ничего особенного, когда дело доходит до того, передаете ли вы его по значению или по ссылке - я действительно не согласен с этим. Это особенное. Лично я предпочел бы перестраховаться и получить небольшое снижение производительности. Если есть конкретная область кода, которую мне нужно оптимизировать, я бы посмотрел на преимущества производительности передачи shared_ptr с помощью const ref. - person JasonZ; 14.02.2012
comment
Также интересно отметить, что, хотя было достигнуто соглашение о чрезмерном использовании shared_ptrs, не было согласия по вопросу передачи по значению по сравнению с эталоном. - person Nicol Bolas; 01.04.2012
comment
С std::vector< shared_ptr<T> > у вас есть std::vector::push_back(std::vector< shared_ptr<T> >::value_type &), то есть контейнер STL shared_ptr принимает ссылку на shared_ptr в своих методах. - person Mike C; 23.05.2012
comment
Скотт Мейерс: так что, если мне удастся обойтись без правильной семантики в моей программе ... т.е. не противоречит моему ответу вообще, что указывает на то, что выяснить, повлияет ли изменение параметров на const & на семантику, очень просто программы. - person Daniel Earwicker; 08.08.2012
comment
Херб Саттер: очень иногда, может быть, потому что вы знаете, что то, что вы вызываете, может изменить то, из чего вы получили ссылку. Опять же, это небольшое исключение для незначительного случая, так что это не противоречит моему ответу. Остается вопрос: как вы знаете, что использовать const ref безопасно? Очень легко доказать в простой программе, не так просто в сложной программе. Но послушайте, это это C ++, и поэтому мы предпочитаем преждевременную микрооптимизацию почти всем остальным инженерным проблемам, верно ?! :) - person Daniel Earwicker; 08.08.2012
comment
Так как совсем недавно я сильно пострадал от этого, я не согласен с этим ответом. Вы можете получить длинную цепочку функций, вызывающих друг друга, каждая из которых берет ссылку на общий указатель, передавая обратно множество функций. Если эта самая внутренняя функция манипулирует коллекцией, в которой был найден общий указатель наверху цепочки, boom, вся цепочка взрывается. Эти вещи могут заразить базу кода, и их будет очень трудно проверить по мере их роста и изменения. Это преждевременная микрооптимизация. - person David Schwartz; 11.03.2013
comment
@David: Вы согласны с тем, что ваша проблема не связана с shared_ptr? Следует ли передавать строку (или любой другой объект) по значению через длинную цепочку функций, потому что самая внутренняя из них может управлять коллекцией, из которой она пришла? - person Jon; 28.03.2013
comment
@Jon: Это не совсем конкретно для shared_ptr. Это относится к конкретным вариантам использования, которые в значительной степени пересекаются с типичными вариантами использования shared_ptr. Одна часть, которая характерна для shared_ptr (или других интеллектуальных указателей), заключается в том, что программисты не всегда думают об объектах, для которых они вызывают методы, так же серьезно, как о параметрах этих методов. Так что это может поразить вас в слепую зону. - person David Schwartz; 28.03.2013
comment
@Jon - re: обновление блога Херба, он снова добавляет очень важную информацию. Обратите внимание, это предполагает, что указатель не имеет псевдонима. И он не вникает в то, что это на самом деле означает, или как это может быть проблемой в реальной программе. Фактически, передача необработанного указателя сама по себе является примером псевдонима, он обходит shared_ptr и оставляет дверь открытой для уничтожения объекта во время использования. И здесь мы видим, что произошло: люди просто читают заголовок экспертного совета. Что ж, современные отладчики настолько хороши, что это нормально, что вы собираетесь проводить там свои вечера! :) - person Daniel Earwicker; 06.07.2013
comment
-1 Хотя эти эксперты и делают такие заявления, они явно неправы, как уже объяснил Дэниел Эрвикер. Писая, я обнаружил, что не согласен с ответом, получившим наибольшее количество голосов, когда ответ, получивший наибольшее количество голосов, явно является подтверждением факта, абсурден. - person J D; 24.10.2013
comment
Я нашел совсем недавний комментарий Херба Саттера очень интересным по этой теме. Если у вас есть функция, о которой вы уже знаете, что другой shared_ptr доминирует в области действия вашего вызова функции. передайте необработанный указатель или ссылку на константу по соображениям производительности (и гибкости, потому что вы не дублируете код, когда у вас есть unique_ptr). - person Alexander Oh; 08.12.2014
comment
В общем, это ужасный совет. В контексте этого вопроса SO вы должны придерживаться передовой практики, которая ... не оптимизируйте заранее. Просто передайте std :: shared_ptr и увеличьте счетчик. Если и только если вы окажетесь в ситуации, когда shared_ptr снижает производительность, а не в обычном случае, вы можете обсудить некоторые продвинутые приемы программирования в особых случаях. - person Cameron Lowell Palmer; 22.02.2015
comment
@CameronLowellPalme: Я не могу не согласиться. Код связан с коммуникацией, и передача shared_ptr сообщает, что функции нужна возможность взаимодействовать со временем жизни объекта, а не только с переданным объектом. Я не могу сформулировать это лучше, чем это сделал Саттер: вообще не передавайте shared_ptr, если вы не хотите использовать или управлять самим интеллектуальным указателем, например, для совместного использования или передачи права собственности. - person JoeG; 31.03.2016
comment
@JoeGauterin Я согласен с тем, что код предназначен для общения и что важно подумать о времени жизни / контракте, связанном с передачей указателя или данных другой функции или классу. Меня беспокоит стоимость shared_ptr. Именно поэтому C ++ будет медленно уходить в безвестность. Здесь преобладают мелочи относительно того, какой из них является наиболее «быстрым» способом сделать x, y или z. Как, если вы закодируете это определенным образом, это приведет к лучшей сборке, испускаемой компилятором. Ответ должен быть ориентирован на контракт, а не на производительность. - person Cameron Lowell Palmer; 31.03.2016
comment
@CameronLowellPalmer, этот спад, вероятно, неизбежен, поскольку C ++ использует микрооптимизацию в качестве центра разработки и, по иронии судьбы, оказывается медленнее, чем современные безопасные языки для широкого круга общих приложений. Учиться интересно, но нет коммерческого смысла в использовании C или C ++, за исключением некоторых ниш. Используйте его, напишите движок JS, JVM или CLR, а затем делайте все остальное в безопасной, быстрой и продуктивной среде. (В последнее время я разрешил мне писать расширения MS Office на JS + HTML, которые работали бы во всех версиях Office). - person Daniel Earwicker; 22.10.2016

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

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

Во-вторых, не оптимизируйте, пока не узнаете, что именно этот класс будет проблемой. Сначала профилируйте, а затем, если ваша программа действительно нуждается в усилении, предоставляемом передачей по ссылке, то, возможно,. В противном случае не переживайте по мелочам (то есть по поводу дополнительных N инструкций, необходимых для передачи по значению), вместо этого беспокойтесь о дизайне, структурах данных, алгоритмах и долгосрочной ремонтопригодности.

person Mark Kegel    schedule 29.11.2008
comment
Хотя ответ litb технически правильный, никогда не стоит недооценивать лень программистов (я тоже ленив!). Ответ littlenag лучше: ссылка на shared_ptr будет неожиданной и, возможно (возможно) ненужной оптимизацией, которая усложнит дальнейшее обслуживание. - person netjeff; 30.11.2008

Да, там взять ссылку можно. Вы не собираетесь передавать методу совместное владение; он только хочет с этим работать. Вы можете взять справочник и для первого случая, так как вы все равно его скопируете. Но в первом случае он принимает право собственности. Есть такая уловка, чтобы скопировать его только один раз:

void ClassA::take_copy_of_sp(boost::shared_ptr<foo> sp) {
    m_sp_member.swap(sp);
}

Вы также должны скопировать, когда вернете его (т. Е. Не вернете ссылку). Потому что ваш класс не знает, что с ним делает клиент (он может сохранить указатель на него, и тогда произойдет большой взрыв). Если позже выяснится, что это узкое место (первый профиль!), Вы все равно можете вернуть ссылку.


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

person Johannes Schaub - litb    schedule 29.11.2008

Разумно передать shared_ptrs на const&. Это вряд ли вызовет проблемы (за исключением маловероятного случая, когда указанный shared_ptr будет удален во время вызова функции, как подробно описано Earwicker), и, вероятно, будет быстрее, если вы передадите много из них. Помните; по умолчанию boost::shared_ptr является потокобезопасным, поэтому его копирование включает потокобезопасное приращение.

Попробуйте использовать const&, а не просто &, потому что временные объекты не могут передаваться по неконстантной ссылке. (Хотя языковое расширение в MSVC все равно позволяет это делать)

person Magnus Hoff    schedule 30.11.2008
comment
Да, я всегда использую константные ссылки, просто забыл вставить это в свой пример. В любом случае, MSVC позволяет привязывать неконстантные ссылки к временным библиотекам не из-за ошибки, а потому, что по умолчанию для свойства C / C ++ - ›Language -› Disable Language Extension установлено значение NO. Включите его, и он их не скомпилирует ... - person abigagli; 30.11.2008
comment
Абигагли: Серьезно? Сладкий! Я сделаю это на работе первым делом завтра;) - person Magnus Hoff; 01.12.2008

Во втором случае сделать это проще:

Class::only_work_with_sp(foo &sp)
{    
    ...  
    sp.do_something();  
    ...  
}

Вы можете назвать это как

only_work_with_sp(*sp);
person Greg Rogers    schedule 29.11.2008
comment
Если вы примете соглашение об использовании объектных ссылок, когда вам не нужно делать копию указателя, это также послужит документированию вашего намерения. Это также дает вам возможность использовать константную ссылку. - person Mark Ransom; 29.11.2008
comment
Да, я согласен с использованием ссылок на объекты как средства выражения того, что вызываемая функция ничего не запоминает об этом объекте. Обычно я использую формальные аргументы указателя, если функция отслеживает объект. - person abigagli; 30.11.2008

Я бы избегал «простой» ссылки, если только функция явно не может изменять указатель.

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

Как правило, вам следует использовать более простой стиль, если у вас нет причин не делать этого. Затем либо используйте const & последовательно, либо добавьте комментарий о том, почему, если вы используете его только в нескольких местах.

person peterchen    schedule 29.11.2008

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

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

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

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

Только мои 2 цента :-)

person zmqiu    schedule 28.03.2010
comment
См. Комментарий Дэвида Шварца выше ... Я преследовал очень серьезную ошибку, которая была связана с передачей ссылки на общий указатель. Код обрабатывал изменение состояния объекта, и когда он заметил, что состояние объекта изменилось, он удалил его из коллекции объектов в предыдущем состоянии и переместил в коллекцию объектов в новом состоянии. Операция удаления уничтожила последний общий указатель на объект. Функция-член была вызвана по ссылке на общий указатель в коллекции. Бум ... - person Jason Harrison; 18.09.2014

Кажется, что все плюсы и минусы здесь можно обобщить на ЛЮБОЙ тип, передаваемый по ссылке, а не только на shared_ptr. На мой взгляд, вы должны знать семантику передачи по ссылке, константной ссылке и значению и правильно ее использовать. Но нет ничего плохого в передаче shared_ptr по ссылке, если только вы не думаете, что все ссылки плохие ...

Чтобы вернуться к примеру:

Class::only_work_with_sp( foo &sp ) //Again, no copy here  
{    
    ...  
    sp.do_something();  
    ...  
}

Откуда вы знаете, что sp.do_something() не взорвется из-за зависшего указателя?

Правда в том, что, shared_ptr или нет, const или нет, это может произойти, если у вас есть недостаток дизайна, например, прямое или косвенное разделение права собственности на sp между потоками, неправильное использование объекта, который делает delete this, у вас есть круговое владение или другое владение ошибки.

person Sandy    schedule 29.11.2010

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

Например, этот код вызовет ошибку, но он будет работать, если вы измените test() так, чтобы общий указатель не передавался по ссылке.

#include <boost/shared_ptr.hpp>

class Base { };
class Derived: public Base { };

// ONLY instances of Base can be passed by reference.  If you have a shared_ptr
// to a derived type, you have to cast it manually.  If you remove the reference
// and pass the shared_ptr by value, then the cast is implicit so you don't have
// to worry about it.
void test(boost::shared_ptr<Base>& b)
{
    return;
}

int main(void)
{
    boost::shared_ptr<Derived> d(new Derived);
    test(d);

    // If you want the above call to work with references, you will have to manually cast
    // pointers like this, EVERY time you call the function.  Since you are creating a new
    // shared pointer, you lose the benefit of passing by reference.
    boost::shared_ptr<Base> b = boost::dynamic_pointer_cast<Base>(d);
    test(b);

    return 0;
}
person Malvineous    schedule 25.09.2014

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

Переход по ссылке разрешен

Передача по константной ссылке лучше и обычно может использоваться, поскольку она не заставляет константность на указанном объекте.

Вы не рискуете потерять указатель из-за использования ссылки. Эта ссылка свидетельствует о том, что у вас есть копия интеллектуального указателя ранее в стеке, и только один поток владеет стеком вызовов, так что уже существующая копия никуда не денется.

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

Вам следует всегда избегать хранения умного указателя в качестве справочного материала. Ваш Class::take_copy_of_sp(&sp) пример показывает правильное использование для этого.

person Drew Dormann    schedule 30.11.2008
comment
Вы не рискуете потерять указатель из-за использования ссылки. Эта ссылка свидетельствует о том, что у вас есть копия интеллектуального указателя ранее в стеке Или член данных ...? - person Daniel Earwicker; 01.12.2008
comment
Рассмотрим магию boost :: thread и boost :: ref: boost :: function ‹int› functionPointer = boost :: bind (doSomething, boost :: ref (sharedPtrInstance)); m_workerThread = новый boost :: thread (functionPointer); ... удалить sharedPtrInstance - person Jason Harrison; 18.09.2014

Предполагая, что нас не интересует правильность констант (или более того, вы имеете в виду, чтобы позволить функциям иметь возможность изменять или разделять владение передаваемыми данными), передача boost :: shared_ptr по значению безопаснее, чем передача его по ссылке как мы позволяем исходному boost :: shared_ptr управлять своим временем жизни. Рассмотрим результаты следующего кода ...

void FooTakesReference( boost::shared_ptr< int > & ptr )
{
    ptr.reset(); // We reset, and so does sharedA, memory is deleted.
}

void FooTakesValue( boost::shared_ptr< int > ptr )
{
    ptr.reset(); // Our temporary is reset, however sharedB hasn't.
}

void main()
{
    boost::shared_ptr< int > sharedA( new int( 13 ) );
    boost::shared_ptr< int > sharedB( new int( 14 ) );

    FooTakesReference( sharedA );

    FooTakesValue( sharedB );
}

Из приведенного выше примера мы видим, что передача sharedA по ссылке позволяет FooTakesReference сбрасывать исходный указатель, что уменьшает его счетчик использования до 0, уничтожая его данные. Однако FooTakesValue не может сбросить исходный указатель, что гарантирует возможность использования данных sharedB. Когда неизбежно приходит другой разработчик и пытается воспользоваться хрупким существованием sharedA, наступает хаос. Удачливый разработчик sharedB, однако, рано отправляется домой, поскольку в его мире все в порядке.

В этом случае безопасность кода намного превосходит любое улучшение скорости копирования. В то же время boost :: shared_ptr предназначен для повышения безопасности кода. Будет намного проще перейти от копии к ссылке, если что-то требует такой оптимизации ниши.

person Kit10    schedule 03.03.2010

Сэнди писал: «Кажется, что все плюсы и минусы здесь можно обобщить на ЛЮБОЙ тип, передаваемый по ссылке, а не только на shared_ptr».

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

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

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

person Bill H    schedule 02.09.2011

Раньше я работал в проекте, в котором был очень строгий принцип передачи интеллектуальных указателей по значению. Когда меня попросили провести некоторый анализ производительности, я обнаружил, что на увеличение и уменьшение счетчиков ссылок интеллектуальных указателей приложение тратит от 4 до 6% используемого процессорного времени.

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

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

person gsf    schedule 11.04.2013

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

person Leon Timmermans    schedule 29.11.2008

struct A {
  shared_ptr<Message> msg;
  shared_ptr<Message> * ptr_msg;
}
  1. пройти по значению:

    void set(shared_ptr<Message> msg) {
      this->msg = msg; /// create a new shared_ptr, reference count will be added;
    } /// out of method, new created shared_ptr will be deleted, of course, reference count also be reduced;
    
  2. пройти по ссылке:

    void set(shared_ptr<Message>& msg) {
     this->msg = msg; /// reference count will be added, because reference is just an alias.
     }
    
  3. пройти по указателю:

    void set(shared_ptr<Message>* msg) {
      this->ptr_msg = msg; /// reference count will not be added;
    }
    
person Dylan Chen    schedule 23.07.2013

Каждый фрагмент кода должен иметь какой-то смысл. Если вы передаете общий указатель по значению везде в приложении, это означает «Я не уверен в том, что происходит в другом месте, поэтому я предпочитаю чистую безопасность». Это не то, что я называю хорошим знаком доверия для других программистов, которые могут ознакомиться с кодом.

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

Так что да, ИМО, по умолчанию должно быть «передать по константной ссылке».

person Philippe    schedule 05.03.2016