Преимущество использования 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>аргумента?