Передача спільних покажчиків як аргументів


88

Якщо я оголошую об'єкт, загорнутий у спільний вказівник:

std::shared_ptr<myClass> myClassObject(new myClass());

тоді я хотів передати це як аргумент методу:

DoSomething(myClassObject);

//the called method
void DoSomething(std::shared_ptr<myClass> arg1)
{
   arg1->someField = 4;
}

Чи вищезгадане просто збільшує кількість посилань на shared_pt, і все круто? Або це залишає звисаючий вказівник?

Ви все ще повинні це зробити ?:

DoSomething(myClassObject.Get());

void DoSomething(std::shared_ptr<myClass>* arg1)
{
   (*arg1)->someField = 4;
}

Я думаю, що 2-й спосіб може бути більш ефективним, оскільки він повинен скопіювати лише 1 адресу (на відміну від цілого розумного вказівника), але 1-й спосіб здається більш читабельним, і я не передбачаю, що розмиваються обмеження продуктивності. Я просто хочу переконатися, що в цьому немає чогось небезпечного.

Дякую.


14
const std::shared_ptr<myClass>& arg1
Капітан Obvlious

3
Другий спосіб не працює, перший є ідіоматичним, якщо вам дійсно потрібна ваша функція для спільного володіння. Але, чи DoSomethingсправді потрібно ділитися власністю? Схоже, замість цього слід просто взяти посилання ...
ildjarn

8
@SteveH: Ні, але чому ваша функція повинна нав'язувати спеціальну семантику власності на об'єкти своїм абонентам, якщо вона їм насправді не потрібна? Ваша функція не робить нічого, що не було б кращим як void DoSomething(myClass& arg1).
ildjarn

2
@SteveH: Уся мета розумних вказівників полягає у вирішенні звичайних питань власності на об’єкти - якщо у вас їх немає, вам не слід спочатку використовувати розумні вказівники. ; -] Що стосується shared_ptr<>конкретно, ви повинні передавати значення, щоб фактично поділити власність.
ildjarn

4
Не пов'язані: загалом, std::make_sharedце не тільки ефективніше, але й безпечніше, ніж std::shared_ptrконструктор.
Р. Мартіньо Фернандес

Відповіді:


171

Я хочу передати спільний покажчик функції. Чи можете ви мені в цьому допомогти?

Звичайно, я можу вам із цим допомогти. Я припускаю, що ви дещо розумієте семантику власності в C ++. Це правда?

Так, мені досить зручно з цим предметом.

Добре.

Гаразд, я можу думати лише про дві причини shared_ptrаргументувати:

  1. Функція хоче поділити право власності на об’єкт;
  2. Функція виконує певну операцію, яка працює спеціально для shared_ptrs.

Який із вас цікавить?

Я шукаю загальної відповіді, тому насправді мене цікавить і те, і інше. Однак мені цікаво, що ви маєте на увазі у випадку №2.

Прикладами таких функцій є std::static_pointer_castкористувацькі порівняльники або предикати. Наприклад, якщо вам потрібно знайти всі унікальні shared_ptr з вектора, вам потрібен такий предикат.

Ах, коли функції насправді потрібно маніпулювати самим розумним вказівником.

Точно так.

У такому випадку, я думаю, нам слід пройти шляхом посилання.

Так. І якщо це не змінює вказівник, ви хочете передати посилання const. Не потрібно копіювати, оскільки вам не потрібно ділитися правом власності. Це інший сценарій.

Добре, зрозумів. Поговоримо про інший сценарій.

Той, де ви ділитесь власністю? В порядку. Як ви ділитеся власністю shared_ptr?

Скопіювавши його.

Тоді функції потрібно буде зробити копію a shared_ptr, правильно?

Очевидно. Тож я передаю його посиланням на const та копіюю до локальної змінної?

Ні, це песимізація. Якщо його передано за посиланням, функції не залишиться іншого вибору, як зробити копію вручну. Якщо воно передається за значенням, компілятор вибере найкращий вибір між копією та переміщенням та виконає його автоматично. Отже, передайте значення.

Гарна думка. Я повинен пам’ятати, що стаття " Хочете швидкість? Пропустіть цінність " частіше.

Зачекайте, а якщо функція зберігає shared_ptr, наприклад, змінну-член? Чи не буде це зробити надлишкову копію?

Функція може просто перемістити shared_ptrаргумент у своє сховище. Переміщення a shared_ptrє дешевим, оскільки це не змінює жодного підрахунку посилань.

Ах, гарна ідея.

Але я думаю про третій сценарій: що робити, якщо ви не хочете маніпулювати цим shared_ptr, ані ділитися власністю?

У такому випадку shared_ptrце абсолютно не має значення для функції. Якщо ви хочете маніпулювати пуантом, візьміть пуанте та дозвольте абонентам вибрати, яку семантику власності вони хочуть.

І чи слід брати пуанте за посиланням чи за значенням?

Застосовуються звичайні правила. Розумні вказівники нічого не змінюють.

Передайте значення, якщо я збираюся скопіювати, передайте посилання, якщо хочу уникнути копіювання.

Правильно.

Хм Я думаю, ви забули ще один сценарій. Що робити, якщо я хочу поділитися власністю, але лише залежно від певної умови?

Ах, цікавий крайній випадок. Я не очікую, що це трапляється часто. Але коли це відбувається, ви можете або передати значення, і ігнорувати копію, якщо вона вам не потрібна, або передати посилання та зробити копію, якщо вам це потрібно.

Я ризикую однією надмірною копією в першому варіанті, а втрачу потенційний хід у другому. Чи не можу я з'їсти торт і його теж взяти?

Якщо ви потрапили в ситуацію, коли це насправді важливо, ви можете забезпечити дві перевантаження, одну з яких бере посилання const lvalue, а іншу - посилання rvalue. Один копіює, інший рухається. Ще один варіант - ідеальний шаблон функції переадресації.

Я думаю, що це охоплює всі можливі сценарії. Дуже дякую.


2
@ Джон: Для чого? Це все про я повинен зробити A , або я повинен зробити B . Я думаю, ми всі знаємо, як передавати об'єкти за значенням / посиланням, ні?
sbi

9
Оскільки проза на кшталт "Чи означає це, що я передаю це посилання на const, щоб зробити копію? Ні, це песимізація", бентежить новачка. Передача посилання на const не робить копію.
Джон

1
@Martinho Я не погоджуюсь з правилом "Якщо ви хочете маніпулювати пуантом, візьміть пуанте". Як зазначено у цій відповіді, не взяти тимчасове право власності на об'єкт може бути небезпечним. На мій погляд, за замовчуванням має бути передача копії для тимчасового володіння для гарантування дійсності об’єкта на час виклику функції.
радман

@radman Жоден сценарій у відповіді, на яку ви посилаєтесь, не застосовує цього правила, тому він абсолютно не має значення. Будь ласка, напишіть мені SSCCE, де ви передаєте посилання від shared_ptr<T> ptr;(тобто void f(T&), викликаного за допомогою f(*ptr)), і об’єкт не переживає виклик. Як варіант, напишіть такий, де ви проходите повз значення void f(T)та страйки.
R. Martinho Fernandes

@Martinho перевірте мій приклад коду для простого правильного прикладу. Приклад - для void f(T&)і включає потоки. Я думаю, що моє питання полягає в тому, що тон вашої відповіді перешкоджає передачі права власності на shared_ptr, що є найбільш безпечним, інтуїтивно зрозумілим і найменш складним способом їх використання. Я був би вкрай проти того, щоб коли-небудь радити новачкові витягти посилання на дані, що належать shared_ptr <>, можливості зловживання великі, а користь мінімальна.
радман

22

Я думаю, що люди без потреби бояться використовувати необроблені покажчики як параметри функції. Якщо функція не збирається зберігати покажчик або іншим чином впливати на час його життя, необроблений покажчик працює так само добре і представляє найменший загальний знаменник. Поміркуйте, наприклад, як ви unique_ptrпередаєте функцію, яка приймає a shared_ptrяк параметр, або за значенням, або за посиланням const?

void DoSomething(myClass * p);

DoSomething(myClass_shared_ptr.get());
DoSomething(myClass_unique_ptr.get());

Сирий вказівник як функціональний параметр не заважає вам використовувати розумні вказівники у викличному коді, де це насправді важливо.


8
Якщо ви використовуєте покажчик, чому б просто не використати посилання? DoSomething(*a_smart_ptr)
Xeo

5
@Xeo, ти маєш рацію - це було б ще краще. Іноді вам потрібно дозволити можливість вказівника NULL.
Марк Ренсом,

1
Якщо у вас є умовність використання сировини покажчиків в якості вихідних параметрів його легше виявити в зухвалій код , що змінний може змінитися, наприклад: порівняння fun(&x)з fun(x). В останньому прикладі xможе бути переданий за значенням або як const ref. Як зазначено вище, це також дозволить вам пройти, nullptrякщо вас не цікавить результат ...
Андреас Магнуссон,

2
@AndreasMagnusson: Ця умова вказівника як вихідного параметра може дати помилкове відчуття безпеки при роботі з вказівниками, переданими з іншого джерела. Оскільки в цьому випадку дзвінок веселий (x), а * x модифікований, і джерело не має & x, щоб попередити вас.
Zan Lynx

що, якщо виклик функції переживає обсяг виклику? Існує ймовірність того, що було викликано деструктор спільного ptr, і тепер функція намагатиметься отримати доступ до видаленої пам'яті, в результаті чого UB
Sridhar

4

Так, вся ідея про shared_ptr <> полягає в тому, що кілька екземплярів можуть містити один і той же вихідний вказівник, і основна пам'ять буде звільнена лише тоді, коли буде знищено останній екземпляр shared_ptr <>.

Я б уникав вказівника на shared_ptr <>, оскільки це перешкоджає меті, оскільки ви знову маєте справу з raw_pointers.


2

Передавання значення у вашому першому прикладі безпечне, але є краща ідіома. Передайте посилання const, коли це можливо - я б сказав так, навіть маючи справу з розумними вказівниками. Ваш другий приклад не зовсім зламаний, але дуже !???. Нерозумно, нічого не виконуючи і перемагаючи частину точки розумних покажчиків, і збираючись залишити вас у баггі-світі болю, коли ви намагаєтеся розмежувати та модифікувати речі.


3
Ніхто не виступає за передачу повзунка, якщо ви маєте на увазі необроблений вказівник. Передача посилання, якщо немає суперечок щодо права власності, безумовно, ідіоматична. Йдеться, shared_ptr<>зокрема, про те, що ви повинні зробити копію, щоб фактично поділити право власності; якщо у вас є посилання або вказівник на, shared_ptr<>то ви нічим не ділитесь, і на вас поширюються ті самі проблеми, що й на звичайне посилання чи вказівник.
ildjarn

2
@ildjarn: Передача постійного посилання в цьому випадку прекрасна. Оригінал shared_ptrабонента гарантовано переживе виклик функції, тому функція безпечна у використанні shared_ptr<> const &. Якщо йому потрібно зберегти покажчик пізніше, йому потрібно буде скопіювати (на цьому етапі потрібно зробити копію, а не зберігати посилання), але немає необхідності нести витрати на копіювання, якщо це не потрібно зробити це. Я б
погодився продати

3
@David: " Передача постійного посилання - це нормально в цьому випадку. " Не в будь-якому розумному API - чому API вимагає розумного типу вказівника, яким він навіть не користується, а не просто бере звичайне посилання на const? Це майже так само погано, як відсутність конст-коректності. Якщо ваша думка полягає в тому, що технічно це нічого не шкодить, то я, звичайно, погоджуюсь, але я не вважаю, що це правильно робити.
ildjarn

2
... якщо, з іншого боку, функції не потрібно продовжувати час життя об'єкта, тоді ви можете просто видалити shared_ptrз інтерфейсу і передати звичайне ( const) посилання на вказаний об'єкт.
Девід Родрігес - дріас

3
@David: Що робити, якщо споживач API shared_ptr<>для початку не використовує ? Тепер ваш API насправді - це просто біль у шиї, оскільки він змушує абонента безглуздо змінювати семантику свого життя лише для того, щоб використовувати його. Я не бачу в цьому нічого, що варто пропагувати, окрім як сказати "це технічно нічого не шкодить". Я не бачу нічого суперечливого щодо того, "якщо вам не потрібне спільне володіння, не використовуйте shared_ptr<>".
ildjarn

0

у своїй функції DoSomething ви змінюєте елемент даних екземпляра класу, myClass тож те, що ви змінюєте, - це керований (необроблений вказівник) об’єкт, а не об’єкт (shared_ptr). Це означає, що у точці повернення цієї функції всі спільні вказівники на керований необроблений вказівник побачать свій елемент даних: myClass::someFieldзмінено на інше значення.

у цьому випадку ви передаєте об'єкт функції з гарантією, що ви не змінюєте його (говорячи про shared_ptr, а не про об'єкт, що належить).

Ідіома, якою це можна виразити, - це: const ref, наприклад

void DoSomething(const std::shared_ptr<myClass>& arg)

Подібним чином ви запевняєте користувача у своїй функції, що не додаєте іншого власника до списку власників необробленого вказівника. Однак ви зберегли можливість зміни основного об'єкта, на який вказує необроблений вказівник.

CAVEAT: Це означає, що якщо якимось чином хтось зателефонує shared_ptr::resetперед тим, як ви викликаєте вашу функцію, і на той момент це останній shared_ptr, що володіє raw_ptr, тоді ваш об'єкт буде знищений, а ваша функція буде маніпулювати звисаючим покажчиком на зруйнований об'єкт. ДУЖЕ НЕБЕЗПЕЧНО !!!

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.