C++ - передающие ссылки на станд.:: shared_ptr или повышение:: shared_ptr

113
задан Null 1 April 2016 в 17:20
поделиться

10 ответов

Точка отличного 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! дважды.

Теперь вперед прибывает г-н Maintainer, который смотрит на код и думает: Эй, тот параметр к [1 120] shared_ptr:

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

, Очевидно, который может быть изменен на:

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

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

, Но настоящая проблема то, что теперь тестовый код покажет неопределенное поведение (в Visual C++ 2 010 сборок отладки, это отказывает).

г-н Maintainer удивлен этим, но добавляет защитную проверку к [1 122] в попытке остановить проблемный случай:

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

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

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

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

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

Примечание, что подобная ошибка произошла бы, если бы мы использовали std::string повсюду вместо [1 132], и вместо:

previous_message = 0;

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

previous_message.clear();

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

109
ответ дан Daniel Earwicker 24 November 2019 в 02:39
поделиться

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

0
ответ дан Leon Timmermans 24 November 2019 в 02:39
поделиться

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

Передача ссылкой хорошо

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

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

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

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

1
ответ дан Drew Dormann 24 November 2019 в 02:39
поделиться

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

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

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

3
ответ дан peterchen 24 November 2019 в 02:39
поделиться

Во втором случае, делая это более просто:

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

можно назвать его как

only_work_with_sp(*sp);
10
ответ дан Greg Rogers 24 November 2019 в 02:39
поделиться

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

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

11
ответ дан Magnus Hoff 24 November 2019 в 02:39
поделиться

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

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

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

<час>

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

18
ответ дан Johannes Schaub - litb 24 November 2019 в 02:39
поделиться

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

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

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

22
ответ дан Mark Kegel 24 November 2019 в 02:39
поделиться

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

1
ответ дан 24 November 2019 в 02:39
поделиться

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

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

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

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

Всего 2 цента: -)

3
ответ дан 24 November 2019 в 02:39
поделиться
Другие вопросы по тегам:

Похожие вопросы: