Зачем использовать boost:: optional, когда я могу вернуть указатель

Если у меня есть функция find, которая иногда не может найти требуемую вещь, я склоняюсь к тому, чтобы эта функция возвращала указатель, так что nullptr указывает, что вещь не была найдена.

E.g.

Student* SomeClass::findStudent(/** some criteria. */)

Если объект Student существует, он вернет указатель на найденный объект Student, в противном случае он вернет nullptr.

Я также видел, как boost::optional выступает за эту цель. Например. Когда использовать boost::Optional и когда использовать std::unique_ptr в тех случаях, когда вы хотите реализовать функцию, которая ничего не возвращает?

Мой вопрос в том, что в этом случае указатель не является лучшим решением. т. е. существует вероятность того, что запрошенный элемент не будет найден, и в этом случае возврат nullptr является идеальным решением. В чем преимущество использования чего-то вроде boost::optional (или любого другого подобного решения)?

Обратите внимание, что в моем примере findStudent будет возвращать указатель только на объект, принадлежащий SomeClass.


person nappyfalcon    schedule 09.02.2016    source источник
comment
Как насчет случая, когда ваша функция find возвращает тип значения, например. int или double?   -  person CompuChip    schedule 09.02.2016
comment
Кроме того, из вашего связанного вопроса stackoverflow.com/a/14360932/2920343   -  person CompuChip    schedule 09.02.2016
comment
@CompuChip Имеет ли это смысл? Вы бы просто вернули двойное значение по значению, верно? Если вы хотите, чтобы пользователь мог редактировать конкретное значение типа double в памяти, принадлежащей SomeClass, вы снова должны вернуть значение типа double*.   -  person nappyfalcon    schedule 09.02.2016
comment
@nappyfalcon Если функция возвращает double по значению (или, лучше, int по значению), и вы хотите указать, что не нашли ее, вы вернете... что?   -  person Angew is no longer proud of SO    schedule 09.02.2016
comment
@nappyfalcon: весь вопрос был о необязательном. что делать, если значение не было найдено?   -  person Karoly Horvath    schedule 09.02.2016
comment
@nappyfalcon С указателем не совсем понятно, кто несет ответственность за его удаление, и что он может быть нулевым и что это значит. optional по крайней мере проясняет ситуацию для пользователя API.   -  person Biffen    schedule 09.02.2016
comment
Хорошо, что касается двойника - так что возможный ответ - это когда вы хотите вернуть что-то по значению, но все же хотите указать, что не найдено. Отлично. Но как насчет общего случая?   -  person nappyfalcon    schedule 09.02.2016
comment
@Biffen, я придерживаюсь правила, согласно которому, если иное не указано большими буквами, ответственность за удаление несет создатель.   -  person nappyfalcon    schedule 09.02.2016
comment
@nappyfalcon Непонятно (по крайней мере мне), кто здесь создатель. Создает ли findStudent() новый объект? Полагаю, что нет, но API этого не передает.   -  person Biffen    schedule 09.02.2016
comment
@nappyfalcon: что происходит, когда пользователь API забывает, что значение является необязательным, и разыменовывает nullptr?   -  person Karoly Horvath    schedule 09.02.2016
comment
@Biffen Для вызывающего это не имеет значения. Но должно быть ясно, что инициатор findStudent не должен владеть возвращенным экземпляром. И если вы думаете, что это не так ясно, то действительно ли использование boost::Optional решает эту проблему?   -  person nappyfalcon    schedule 09.02.2016
comment
@nappyfalcon: optional решает эту проблему либо путем копирования результата по значению вызывающей стороне (или, возможно, перемещения его в С++ 11). Право собственности теперь принадлежит вызывающей стороне в форме временного объекта, который затем уничтожается таким образом, который имеет четкую семантику.   -  person marko    schedule 09.02.2016
comment
@nappyfalcon Я просто пытаюсь ответить на вопрос, и, насколько я понимаю, самое большое преимущество optional не столько в техническом плане, сколько в том, чтобы разработчикам было понятнее, что это тип, который может иметь или не иметь стоимость. У указателей много недостатков. Я не буду перечислять их здесь, но optional по крайней мере имеет RAII, которого нет (необработанных) указателей. Возможно, вы представляете идеальный вариант использования указателей, но, по мнению некоторых людей, у optional есть и другие.   -  person Biffen    schedule 09.02.2016


Ответы (4)


Преимущество возвращаемого типа optional<Student&> здесь заключается в том, что семантика использования очевидна для всех пользователей, знакомых с optional (и станет очевидной, как только они ознакомятся с ним). Эти семантики:

  • Вызывающий не владеет Student и не отвечает за управление памятью. Вызывающий просто получает ссылку на существующий объект.
  • Понятно, что эта функция может дать сбой. Вы может быть получаете ценность, но может быть ничего не получаете. Понятно, что вызывающему нужно так или иначе проверить результат.

optional<T> самодокументируется в отличие от T*. Более того, у него есть и другие преимущества: он может работать в тех случаях, когда вы хотите вернуть объект любого типа без необходимости выделения памяти. Что, если вам нужно вернуть int, double или SomePOD?

person Barry    schedule 09.02.2016
comment
optional<T&> явно недопустимо в текущей версии типа TS. optional для типов значений. - person Nicol Bolas; 09.02.2016
comment
Хм, тогда для этого потребуется optional<reference_wrapper<T>>. Уродливый. - person Angew is no longer proud of SO; 09.02.2016
comment
@Энгью make_optional(std::ref(x)) - person bolov; 09.02.2016
comment
@bolov Я имел в виду, когда указывал тип возвращаемого значения. Предположим, что auto здесь не вариант. С decltype можно поколдовать, но это тоже некрасиво. - person Angew is no longer proud of SO; 09.02.2016
comment
@Barry: Потому что у нас это уже есть; он называется указателем. optional для типов значений. - person Nicol Bolas; 09.02.2016
comment
@NicolBolas optional для возможных типов. Почему ненужное ограничение на значения? Кроме того, как тогда вообще можно observer_ptr<T> брать во внимание? - person Barry; 09.02.2016
comment
@Barry: Кроме того, как тогда можно вообще рассматривать наблюдатель_ptr‹T›? Обратите внимание, что Основные рекомендации C++ идут другим путем в отношении типов указателей. То есть T* предполагается невладельческим, и если вам нужно представить принадлежащий указатель без интеллектуального указателя, вы используете owned<T>. - person Nicol Bolas; 09.02.2016

optional<T&> был удален из стандартизации C++, потому что его использование сомнительно: он ведет себя почти идентично не владеющему T* с немного другой (и до путаницы отличной от optional<T> и T*) семантикой.

optional<T&> в основном не владеющий T* красиво и несколько странно завернутый.


Теперь optional<T> другой зверь.

Я использовал optional<Iterator> в своих алгоритмах поиска на основе контейнеров. Вместо того, чтобы возвращать end(), я возвращаю пустой необязательный параметр. Это позволяет пользователям определить без сравнения, не удалось ли им найти элемент, и позволяет использовать код, подобный следующему:

if(linear_search_for( vec, item))

работать, в то время как тот же алгоритм также позволяет вам получить как элемент , так и местоположение элемента в контейнере, если он вам действительно нужен.

Указатели на элементы не дают вам информации о местоположении, которая может вам понадобиться, за исключением смежных контейнеров.

Итак, здесь я создал итератор, допускающий значение NULL, который имеет преимущества итераторов (как правило, работающих с контейнерами разных типов) и указателей (можно проверить на нулевое состояние).

Следующее использование фактически возвращает значение. Предположим, у вас есть функция, которая вычисляет прямоугольник.

Rect GetRect();

теперь это здорово. Но что, если вопрос может быть бессмысленным? Что ж, один из подходов состоит в том, чтобы вернуть пустой прямоугольник или другое значение «флаг».

Необязательный позволяет вам сообщить, что он может вернуть прямоугольник или ничего, и не использовать пустой прямоугольник для состояния «ничего». Это делает возвращаемое значение обнуляемым.

int GetValue();

является лучшим примером. Недопустимое значение может использовать состояние флага int, скажем, -1, но это заставляет каждого пользователя вашей функции искать и отслеживать состояние флага, а не случайно рассматривать его как нормальное состояние.

Вместо этого optional<int> GetValue() ясно дает понять, что может произойти сбой, и что означает сбой. Если он заполнен, вы знаете, что это реальное значение, а не значение флага.

В обоих этих случаях возврат указателя, не являющегося владельцем, нецелесообразен, потому что кому принадлежит хранилище? Возврат указателя-владельца обходится дорого, потому что бессмысленное выделение кучи бессмысленно.

Необязательные типы — это типы значений, допускающие значение NULL. Когда вы хотите управлять ресурсами локально, и вам все еще нужно пустое состояние, они ясно дают понять.


Еще одна вещь, на которую стоит обратить внимание, это предлагаемый тип expected. Это необязательно, но в пустом состоянии содержит причину, почему он пуст.

person Yakk - Adam Nevraumont    schedule 09.02.2016
comment
Мне нравится, потому что бессмысленное выделение кучи бессмысленно — потому что глупо — это глупо. Хорошо сказано, сэр! - person Cherusker; 06.01.2019

optional<T&> действительно может быть заменен на T*, но T* не имеет четкой семантики (право собственности?).

Но optional<T> нельзя заменить на T*. Например:

optional<Interval> ComputeOverlap(const Interval&, const Interval&);

Если нет перекрытия, нет проблем с T* (nullptr) или optional<T>. Но если есть перекрытие, нам нужно создать новый интервал. В этом случае мы можем вернуть smart_pointer или опционально.

person Jarod42    schedule 09.02.2016

Предположим, у вас есть std::map<IndexType, ValueType>, где вы пытаетесь что-то найти (Примечание: то же самое относится и к другим контейнерам, это просто пример). У вас есть следующие варианты:

  • Вы возвращаете ValueType&: пользователь может изменить содержимое вашей карты, и ему не нужно думать о выделении/освобождении памяти. Но если вы ничего не найдете на своей карте, вам нужно создать исключение или что-то подобное.
  • Вы возвращаете ValueType*: пользователь может изменить содержимое вашей карты, и вы можете вернуть nullptr, если ничего не найдете. Но пользователь может вызвать удаление для этого указателя, и вы все равно должны указать, должен он это делать или нет.
  • Вы возвращаете смарт-указатель на ValueType: пользователю не нужно беспокоиться об удалении или отказе от удаления, и он может изменять содержимое вашей карты в зависимости от типа смарт-указателя. Вы также можете вернуть nullptr. Но это в значительной степени требует, чтобы вы имели дело с smart_pointers на вашей карте, что слишком сложно, если ValueType будет, например. просто int в противном случае.
  • Вы возвращаете простое ValueType: пользователь не может изменять содержимое вашей карты и ему не нужно думать о выделении/освобождении памяти. Но если вы ничего не нашли на своей карте, вам нужно вернуть какой-то специальный ValueType, который сообщает пользователю, что вы ничего не нашли. Если ваш ValueType, например. int, какой из них вы бы вернули, чтобы было ясно, что "целое не найдено".
  • Вы возвращаете boost::Optional, который наиболее близок к простому возврату ValueType по значению с дополнительной опцией «не возвращать ValueType»
person Anedar    schedule 09.02.2016
comment
Для первых 2 случаев: вы можете вернуть константную ссылку или указатель, чтобы пользователь не мог их изменить. - person Nuclear; 29.04.2021