Преимущество использования 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 не может быть разыменован и может иметь семантику «ничего» или «без объекта». ( плохо )
- Адрес блока памяти, который находится за пределами адресного пространства вашего процесса (разыменование его, мы надеемся, приведет к сбою вашей программы). ( уродливый )
- Адрес блока памяти, который можно разыменовать, но который не содержит того, что вы ожидаете. Возможно, указатель был случайно изменен и теперь указывает на другой доступный для записи адрес (совершенно другой переменной в вашем процессе). Запись в эту ячейку памяти порой вызывает массу удовольствия во время выполнения, потому что ОС не будет жаловаться, пока вам разрешено писать туда. ( Zoinks! )
Правильное использование интеллектуальных указателей облегчает довольно пугающие случаи 3 и 4, которые обычно не обнаруживаются во время компиляции и которые вы обычно испытываете только во время выполнения, когда ваша программа дает сбой или делает неожиданные вещи.
Передача интеллектуальных указателей в качестве аргументов имеет два недостатка: вы не можете изменить const
-ность указанного объекта, не сделав копию (что добавляет накладные расходы, shared_ptr
а это невозможно для unique_ptr
), и у вас по-прежнему остается значение second ( nullptr
).
Я отметил второй случай как ( плохой ) с точки зрения дизайна. Это более тонкий аргумент об ответственности.
Представьте, что это означает, когда функция получает в nullptr
качестве параметра. Сначала он должен решить, что с ним делать: использовать «магическое» значение вместо пропавшего объекта? полностью изменить поведение и вычислить что-то еще (для чего не нужен объект)? запаниковать и выбросить исключение? Более того, что происходит, когда функция принимает 2, 3 или даже больше аргументов по необработанному указателю? Он должен проверить каждую из них и соответствующим образом адаптировать свое поведение. Это добавляет совершенно новый уровень поверх проверки ввода без реальной причины.
Звонящий должен обладать достаточной контекстной информацией, чтобы принимать эти решения, или, другими словами, чем больше вы знаете , тем меньше пугает плохое . Функция, с другой стороны, должна просто принимать обещание вызывающего абонента о том, что указанная память безопасна для работы по назначению. (Ссылки по-прежнему являются адресами памяти, но концептуально представляют собой обещание действительности.)
std::unique_ptr
качествеstd::vector<std::unique_ptr>
аргумента?