Смысл отдельного экземпляра 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
от этого объекта? Пока, объект, именно там, где вы собираетесь его использовать.
Итак, есть два способа ответить на этот вопрос:
Внимательно изучите источник всей вашей программы, пока не убедитесь, что объект не погибнет в теле функции.
Измените параметр обратно на отдельный объект вместо ссылки.
Общие советы, которые применимы здесь: не беспокойтесь о внесении рискованных изменений в свой код для повышения производительности, пока вы не рассчитали свой продукт в реалистичной ситуации в профилировщике и окончательно не решили, что изменение, которое вы хотите внести будет иметь существенное значение для производительности.
Обновление для комментатора 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
никогда не становится нулевым, когда вызывается 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
, поэтому компромисс может быть другим.