Перевага використання 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 {
}
Це означає, що будь-який абонент повинен пообіцяти, що посилання є дійсним і воно буде продовжувати діяти протягом усього виконання функції функції.
Ось причина, чому я твердо переконаний, що ви не повинні передавати необроблені вказівники або посилання на розумні вказівники.
Сирий вказівник - це лише адреса пам'яті. Може мати одне з (принаймні) 4 значень:
- Адреса блоку пам'яті, де знаходиться бажаний об'єкт. ( добре )
- Адреса 0x0, у якій ви можете бути впевнені, не підлягає переспрямуванню і може мати семантику "нічого" або "немає об'єкта". ( поганий )
- Адреса блоку пам’яті, що знаходиться за межами адресного простору вашого процесу (при розмежуванні посилань це призведе до збою програми). ( потворний )
- Адреса блоку пам'яті, який може бути розменований, але який не містить того, що ви очікуєте. Можливо, вказівник був випадково змінений, і тепер він вказує на іншу адресу для запису (зовсім іншої змінної у вашому процесі). Запис у це місце пам’яті може спричиняти багато задоволення, часом, під час виконання, оскільки ОС не скаржиться, доки вам дозволено там писати. ( Зойнкс! )
Правильне використання розумних покажчиків полегшує досить страшні випадки 3 і 4, які зазвичай не виявляються під час компіляції і які, як правило, виникають лише під час виконання програми, коли програма аварійно завершує роботу або робить несподівані дії.
Передача смарта - покажчиків в якості аргументів має два недоліки: ви не можете змінити const
-ness від загостреного об'єкта без створення копії (яка додає накладні витрати на shared_ptr
і не є можливим unique_ptr
), і ви все ще залишаєтеся з другим ( nullptr
) змістом.
Другий випадок я позначив як ( поганий ) з точки зору дизайну. Це більш тонкий аргумент щодо відповідальності.
Уявіть, що це означає, коли функція отримує nullptr
як свій параметр. Спочатку він повинен вирішити, що з цим робити: використовувати "магічне" значення замість відсутнього предмета? повністю змінити поведінку і обчислити щось інше (для чого об’єкт не потрібен)? панікувати і кинути виняток? Більше того, що відбувається, коли функція приймає 2, 3 або навіть більше аргументів за необробленим покажчиком? Він повинен перевірити кожного з них і відповідно адаптувати свою поведінку. Це додає абсолютно новий рівень поверх перевірки введення без реальних причин.
Той, хто телефонує, повинен мати достатню кількість контекстної інформації для прийняття таких рішень, або, іншими словами, погане менше лякає, чим більше ти знаєш. З іншого боку, функція повинна просто приймати обіцянку абонента, що пам’ять, на яку вона вказує, безпечна для роботи, як передбачалося. (Посилання все ще є адресами пам'яті, але концептуально представляють обіцянку дійсності.)
std::unique_ptr
дляstd::vector<std::unique_ptr>
аргументу?