Смысл отдельного 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
}
Таким образом, используя ссылку на a 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
от этого объекта? До свидания, объект, именно там, где вы собираетесь его использовать.
Итак, есть два способа ответить на этот вопрос:
Очень внимательно изучите источник всей вашей программы, пока не убедитесь, что объект не умрет во время выполнения тела функции.
Измените параметр обратно на отдельный объект, а не на ссылку.
Общий совет, который здесь применим: не утруждайтесь внесением рискованных изменений в свой код ради производительности, пока вы не рассчитали время своего продукта в реалистичной ситуации в профилировщике и окончательно не измерили, что изменение, которое вы хотите внести, приведет к значительная разница в производительности.
Обновление для комментатора JQ
Вот надуманный пример. Это заведомо просто, поэтому ошибка будет очевидна. На реальных примерах ошибка не так очевидна, потому что она скрыта слоями реальных деталей.
У нас есть функция, которая куда-то отправит сообщение. Это может быть большое сообщение, поэтому вместо того, чтобы использовать a, std::string
который, вероятно, копируется, когда он передается в несколько мест, мы используем a 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
постоянное значение, отличное от нуля, заключается в том, чтобы функция выделяла собственное значение true shared_ptr
, а не полагалась на ссылку на существующий shared_ptr
.
Обратной стороной является то, что скопированный a shared_ptr
не является бесплатным: даже «безблокировочные» реализации должны использовать заблокированную операцию для соблюдения гарантий многопоточности. Таким образом, могут возникнуть ситуации, когда программу можно значительно ускорить, заменив a shared_ptr
на shared_ptr &
. Но это изменение не может быть безопасно внесено во все программы. Это меняет логический смысл программы.
Обратите внимание, что аналогичная ошибка возникла бы, если бы мы использовали std::string
повсюду вместо std::shared_ptr<std::string>
и вместо:
previous_message = 0;
чтобы очистить сообщение, мы сказали:
previous_message.clear();
Тогда симптомом будет случайная отправка пустого сообщения вместо неопределенного поведения. Стоимость дополнительной копии очень большой строки может быть намного более значительной, чем стоимость копирования shared_ptr
, поэтому компромисс может быть другим.