Вопрос

Если у меня есть функция, которая должна работать с 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 состоит в том, чтобы (насколько это возможно) гарантировать, что пока этот sp->do_something() находится в области видимости, объект, на который он указывает, будет существовать, поскольку его счетчик ссылок будет по крайней мере 1.

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

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

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

Откуда вы знаете, что Hi! не взорвется из-за нулевого указателя?

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

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

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

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

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

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

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

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

    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);
    

    И, как и ожидалось, он печатает <=> дважды.

    Теперь приходит г-н Мейнтейнер, который смотрит на код и думает: эй, этот параметр для <=> является <=>:

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

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

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

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

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

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

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

    Но, конечно, это все еще происходит и дает сбой, потому что <=> никогда не является нулевым, когда вызывается <=>.

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

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

    Недостатком является то, что копирование <=> не является бесплатным: даже " без блокировки & Quot; Реализации должны использовать блокированную операцию для соблюдения гарантий многопоточности. Таким образом, могут возникнуть ситуации, когда программу можно значительно ускорить, изменив <=> на <=>. Но это не то изменение, которое можно безопасно внести во все программы. Это меняет логический смысл программы.

    Обратите внимание, что похожая ошибка возникнет, если мы будем использовать <=> везде вместо <=> и вместо:

    previous_message = 0;
    

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

    previous_message.clear();
    

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

    Другие советы

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

    Херб Саттер:«когда вы передаете Shared_ptr, копии стоят дорого»

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

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

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

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

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

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

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

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

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

    <Ч>

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

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

    Попробуйте использовать <=>, а не просто <=>, поскольку временные объекты не могут передаваться по неконстантной ссылке. (Даже если расширение языка в MSVC позволяет вам сделать это в любом случае)

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

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

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

    only_work_with_sp(*sp);
    

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

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

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

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

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

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

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

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

    Кажется, что все плюсы и минусы на самом деле могут быть обобщены на ЛЮБОЙ тип, передаваемый по ссылке, а не просто 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, вы иметь круговую собственность или другие ошибки собственности.

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

    Например, этот код выдаст ошибку, но он будет работать, если вы измените 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;
    }
    

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    struct A {
      shared_ptr<Message> msg;
      shared_ptr<Message> * ptr_msg;
    }
    
    <Ол>
  • передать по значению:

    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;
    
  • передать по ссылке:

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

    void set(shared_ptr<Message>* msg) {
      this->ptr_msg = msg; /// reference count will not be added;
    }
    
  • Каждый фрагмент кода должен нести какой-то смысл.Если вы передаете общий указатель по значению повсюду в приложении это означает «Я не уверен в том, что происходит в других местах, поэтому я предпочитаю абсолютную безопасность.".Это не то, что я называю знаком доверия другим программистам, которые могли бы ознакомиться с кодом.

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

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

    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top