Суть різного 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()
не вибухне через нульовий покажчик?
Все залежить від того, що є в тих розділах коду "...". Що робити, якщо ви зателефонуєте щось під час першого "...", що має побічний ефект (десь в іншій частині коду) очищення a shared_ptr
до цього самого об'єкта? А що, якщо станеться єдиним, що залишився shared_ptr
на цьому об'єкті? До побачення об’єкт, саме там, де ви збираєтесь його спробувати і використати.
Отже, є два варіанти відповіді на це питання:
Вивчіть джерело всієї вашої програми дуже уважно, поки ви не переконаєтесь, що об'єкт не загине під час роботи функції.
Змініть параметр назад, щоб він був окремим об'єктом замість посилання.
Загальна порада, яка застосовується тут: не переймайтеся внесенням ризикових змін у свій код заради продуктивності, поки ви не приурочили ваш продукт до реалістичної ситуації в профіле і остаточно не виміряли, що зміна, яку ви хочете внести, внесе значна різниця в продуктивності.
Оновлення для коментатора JQ
Ось надуманий приклад. Це навмисно просто, тому помилка буде очевидною. У реальних прикладах помилка не настільки очевидна, оскільки вона прихована в шарах реальної деталі.
У нас є функція, яка відправить повідомлення кудись. Це може бути велике повідомлення, тому замість того, std::string
щоб скопіювати те, що ймовірно, буде скопійовано під час передачі в декілька місць, ми використовуємо shared_ptr
рядок a:
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, який дивиться на код і думає: Ей, цей параметр 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 налагодження будується, він руйнується).
Г-н Maintainer здивований цим, але додає захисну перевірку 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
.
Мінус полягає в тому, що скопійований a shared_ptr
не є безкоштовним: навіть "без блокування" реалізація повинна використовувати заблоковану операцію, щоб виконувати гарантії нитки. Тож можуть виникнути ситуації, коли програму можна значно прискорити, змінивши shared_ptr
на «а» shared_ptr &
. Але це не та зміна, яку можна сміливо внести до всіх програм. Це змінює логічне значення програми.
Зауважте, що аналогічна помилка може виникнути, якщо ми використовувались у std::string
всьому std::shared_ptr<std::string>
, а не:
previous_message = 0;
щоб очистити повідомлення, ми сказали:
previous_message.clear();
Тоді симптомом буде випадкове надсилання порожнього повідомлення замість невизначеної поведінки. Вартість додаткової копії дуже великого рядка може бути набагато значно більшою, ніж вартість копіювання файлу shared_ptr
, тому компроміс може бути різним.