У меня первая попытка использовать 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
качестве ссылки, то, что было бы ссылкой на ссылку. Во-вторых, потому что сигнатура функции становится еще больше. В-третьих, потому что в сгенерированном коде для достижения моей переменной потребовались бы два последовательных указателя.
источник
std::unique_ptr
качествеstd::vector<std::unique_ptr>
аргумента?Преимущество использования
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 значений:
Правильное использование интеллектуальных указателей облегчает довольно пугающие случаи 3 и 4, которые обычно не обнаруживаются во время компиляции и которые вы обычно испытываете только во время выполнения, когда ваша программа дает сбой или делает неожиданные вещи.
Передача интеллектуальных указателей в качестве аргументов имеет два недостатка: вы не можете изменить
const
-ность указанного объекта, не сделав копию (что добавляет накладные расходы,shared_ptr
а это невозможно дляunique_ptr
), и у вас по-прежнему остается значение second (nullptr
).Я отметил второй случай как ( плохой ) с точки зрения дизайна. Это более тонкий аргумент об ответственности.
Представьте, что это означает, когда функция получает в
nullptr
качестве параметра. Сначала он должен решить, что с ним делать: использовать «магическое» значение вместо пропавшего объекта? полностью изменить поведение и вычислить что-то еще (для чего не нужен объект)? запаниковать и выбросить исключение? Более того, что происходит, когда функция принимает 2, 3 или даже больше аргументов по необработанному указателю? Он должен проверить каждую из них и соответствующим образом адаптировать свое поведение. Это добавляет совершенно новый уровень поверх проверки ввода без реальной причины.Звонящий должен обладать достаточной контекстной информацией, чтобы принимать эти решения, или, другими словами, чем больше вы знаете , тем меньше пугает плохое . Функция, с другой стороны, должна просто принимать обещание вызывающего абонента о том, что указанная память безопасна для работы по назначению. (Ссылки по-прежнему являются адресами памяти, но концептуально представляют собой обещание действительности.)
источник
Я согласен с Мартиньо, но думаю, что важно указать на семантику владения при передаче по ссылке. Я думаю, что правильным решением будет использовать здесь простой переход по ссылке:
bool func(BaseClass& base, int other_arg);
Общепринятое значение передачи по ссылке в C ++ похоже на то, как если бы вызывающий функцию сообщает функции «здесь, вы можете заимствовать этот объект, использовать его и изменять его (если не const), но только для продолжительность функции тела ". Это никоим образом не противоречит правилам владения,
unique_ptr
поскольку объект просто заимствуется на короткий период времени, фактическая передача права собственности не происходит (если вы одалживаете свою машину кому-то, подписываете ли вы право собственности к нему?).Таким образом, даже если это может показаться плохим (с точки зрения дизайна, практики кодирования и т. Д.), Извлекать ссылку (или даже необработанный указатель) из файла, на
unique_ptr
самом деле это не так, потому что это полностью соответствует правилам владения, установленным файлunique_ptr
. И, конечно же, есть и другие приятные преимущества, такие как чистый синтаксис, отсутствие ограничений только на объекты, принадлежащие aunique_ptr
и т. Д.источник
Лично я избегаю извлечения ссылки из указателя / умного указателя. Потому что что будет, если указатель будет
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
накладные расходы нулевые, верно?std::shared_ptr
накладных расходах.