C ++ - передача ссылок на std :: shared_ptr или boost :: shared_ptr - PullRequest
113 голосов
/ 29 ноября 2008

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

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

Ответы [ 17 ]

1 голос
/ 02 сентября 2011

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

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

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

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

1 голос
/ 30 ноября 2008

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

Передача по ссылке в порядке

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

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

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

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

1 голос
/ 11 апреля 2013

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

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

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

1 голос
/ 03 марта 2010

Предполагая, что нас не заботит правильность констант (или больше, вы имеете в виду, что функции должны иметь возможность изменять или совместно использовать владение передаваемыми данными), передавая значение 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 предназначен для повышения безопасности кода. Будет гораздо проще перейти от копии к ссылке, если что-то потребует такой нишевой оптимизации.

0 голосов
/ 29 ноября 2008

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

0 голосов
/ 05 марта 2016

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

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

Так что да, IMO, по умолчанию должно быть " передать по константной ссылке ".

0 голосов
/ 23 июля 2013
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;
    }
    
...