Як пройти std :: unique_ptr?


83

У мене перша спроба використовувати C ++ 11 unique_ptr; Я замінюю поліморфний сирий вказівник всередині мого проекту, який належить одному класу, але передається досить часто.

Раніше у мене були такі функції, як:

bool func(BaseClass* ptr, int other_arg) {
  bool val;
  // plain ordinary function that does something...
  return val;
}

Але незабаром я зрозумів, що не зможу перейти на:

bool func(std::unique_ptr<BaseClass> ptr, int other_arg);

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

Я хоч передав покажчик як посилання, наприклад:

bool func(const std::unique_ptr<BaseClass>& ptr, int other_arg);

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

Відповіді:


97

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

bool func(BaseClass& base, int other_arg);

А на сайті дзвінка використовуйте operator*:

func(*some_unique_ptr, 42);

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

bool func(BaseClass* base, int other_arg);
func(some_unique_ptr.get(), 42);

4
Це приємно, але кулаком ви позбудетеся std::unique_ptrдля std::vector<std::unique_ptr>аргументу?
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

31

Перевага використання std::unique_ptr<T>(крім того, що не потрібно пам’ятати про дзвінок deleteабо delete[]явно) полягає в тому, що воно гарантує, що покажчик є nullptrабо вказує на дійсний екземпляр (базового) об’єкта. Я повернусь до цього після того, як відповім на ваше запитання, але перше повідомлення - ВЖЕ використовувати розумні вказівники для управління часом життя динамічно розподілених об’єктів.

Тепер ваша проблема насправді полягає в тому, як використовувати це зі своїм старим кодом .

Моя порада полягає в тому, що якщо ви не хочете передавати або передавати право власності, ви завжди повинні передавати посилання на об’єкт. Оголосіть свою функцію так (із необхідністю або без constкваліфікаторів):

bool func(BaseClass& ref, int other_arg) { ... }

Тоді абонент, який має a std::shared_ptr<BaseClass> ptr, або розгляне nullptrсправу, або попросить bool func(...)обчислити результат:

if (ptr) {
  result = func(*ptr, some_int);
} else {
  /* the object was, for some reason, either not created or destroyed */
}

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


Ось причина, чому я твердо переконаний, що ви не повинні передавати необроблені вказівники або посилання на розумні вказівники.

Сирий вказівник - це лише адреса пам'яті. Може мати одне з (принаймні) 4 значень:

  1. Адреса блоку пам'яті, де знаходиться бажаний об'єкт. ( добре )
  2. Адреса 0x0, у якій ви можете бути впевнені, не підлягає переспрямуванню і може мати семантику "нічого" або "немає об'єкта". ( поганий )
  3. Адреса блоку пам’яті, що знаходиться за межами адресного простору вашого процесу (при розмежуванні посилань це призведе до збою програми). ( потворний )
  4. Адреса блоку пам'яті, який може бути розменований, але який не містить того, що ви очікуєте. Можливо, вказівник був випадково змінений, і тепер він вказує на іншу адресу для запису (зовсім іншої змінної у вашому процесі). Запис у це місце пам’яті може спричиняти багато задоволення, часом, під час виконання, оскільки ОС не скаржиться, доки вам дозволено там писати. ( Зойнкс! )

Правильне використання розумних покажчиків полегшує досить страшні випадки 3 і 4, які зазвичай не виявляються під час компіляції і які, як правило, виникають лише під час виконання програми, коли програма аварійно завершує роботу або робить несподівані дії.

Передача смарта - покажчиків в якості аргументів має два недоліки: ви не можете змінити const-ness від загостреного об'єкта без створення копії (яка додає накладні витрати на shared_ptrі не є можливим unique_ptr), і ви все ще залишаєтеся з другим ( nullptr) змістом.

Другий випадок я позначив як ( поганий ) з точки зору дизайну. Це більш тонкий аргумент щодо відповідальності.

Уявіть, що це означає, коли функція отримує nullptrяк свій параметр. Спочатку він повинен вирішити, що з цим робити: використовувати "магічне" значення замість відсутнього предмета? повністю змінити поведінку і обчислити щось інше (для чого об’єкт не потрібен)? панікувати і кинути виняток? Більше того, що відбувається, коли функція приймає 2, 3 або навіть більше аргументів за необробленим покажчиком? Він повинен перевірити кожного з них і відповідно адаптувати свою поведінку. Це додає абсолютно новий рівень поверх перевірки введення без реальних причин.

Той, хто телефонує, повинен мати достатню кількість контекстної інформації для прийняття таких рішень, або, іншими словами, погане менше лякає, чим більше ти знаєш. З іншого боку, функція повинна просто приймати обіцянку абонента, що пам’ять, на яку вона вказує, безпечна для роботи, як передбачалося. (Посилання все ще є адресами пам'яті, але концептуально представляють обіцянку дійсності.)


25

Я погоджуюся з Мартіньо, але я думаю, що важливо вказати на семантику власності прохідного посилання. Я вважаю, що правильним рішенням є використання простого посилання тут:

bool func(BaseClass& base, int other_arg);

Загальновизнане значення передавального посилання в C ++ схоже на те, ніби виклик функції повідомляє функції "тут, ви можете запозичити цей об'єкт, використовувати його та модифікувати (якщо не const), але лише для тривалість функції функції ". Це жодним чином не суперечить правилам власності, unique_ptrоскільки об’єкт просто позичається на короткий проміжок часу, фактичної передачі права власності не відбувається (якщо ви позичаєте свою машину комусь, чи підписуєте ви право власності) до нього?).

Отже, хоча це може здатися поганим (з точки зору дизайну, практики кодування тощо) витягувати посилання (або навіть вихідний вказівник) з unique_ptr, насправді це не тому, що воно цілком відповідає правилам власності, встановленим unique_ptr. І тоді, звичайно, є й інші приємні переваги, такі як чистий синтаксис, відсутність обмежень лише на об’єкти, якими володіє a unique_ptr, і так далі.


0

Особисто я уникаю витягування посилання з покажчика / розумного вказівника. Бо що станеться, якщо вказівник є nullptr? Якщо змінити підпис на такий:

bool func(BaseClass& base, int other_arg);

Можливо, вам доведеться захистити свій код від перенаправлення нульових покажчиків:

if (the_unique_ptr)
  func(*the_unique_ptr, 10);

Якщо клас є єдиним власником вказівника, другий з альтернативних варіантів Мартіньо здається більш розумним:

func(the_unique_ptr.get(), 10);

Крім того, ви можете використовувати std::shared_ptr. Однак, якщо відповідає одна організація delete, std::shared_ptrнакладні витрати не окупляться.


Чи знаєте ви, що у більшості випадків std::unique_ptrнакладні витрати дорівнюють нулю, так?
lvella

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