Как передать std :: unique_ptr?

85

У меня первая попытка использовать 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качестве ссылки, то, что было бы ссылкой на ссылку. Во-вторых, потому что сигнатура функции становится еще больше. В-третьих, потому что в сгенерированном коде для достижения моей переменной потребовались бы два последовательных указателя.

lvella
источник

Ответы:

99

Если вы хотите, чтобы функция использовала указатель, передайте на него ссылку. Нет смысла привязывать функцию к работе только с каким-то умным указателем:

bool func(BaseClass& base, int other_arg);

А на месте звонка используйте operator*:

func(*some_unique_ptr, 42);

В качестве альтернативы, если baseаргументу разрешено иметь значение null, оставьте подпись как есть и используйте get()функцию-член:

bool func(BaseClass* base, int other_arg);
func(some_unique_ptr.get(), 42);
Р. Мартиньо Фернандес
источник
4
Это хорошо, но можно ли избавиться от него в std::unique_ptrкачестве std::vector<std::unique_ptr>аргумента?
Чиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
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. Адрес блока памяти, который можно разыменовать, но который не содержит того, что вы ожидаете. Возможно, указатель был случайно изменен и теперь указывает на другой доступный для записи адрес (совершенно другой переменной в вашем процессе). Запись в эту ячейку памяти порой вызывает массу удовольствия во время выполнения, потому что ОС не будет жаловаться, пока вам разрешено писать туда. ( Zoinks! )

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

Передача интеллектуальных указателей в качестве аргументов имеет два недостатка: вы не можете изменить const-ность указанного объекта, не сделав копию (что добавляет накладные расходы, shared_ptrа это невозможно для unique_ptr), и у вас по-прежнему остается значение second ( 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