2013-05-31 3 views
91

Я читаю документацию std::experimental::optional, и у меня есть хорошее представление о том, что он делает, но я не понимаю , когда Я должен использовать его или как его использовать. На сайте пока нет каких-либо примеров, которые затрудняют мне понимание истинной концепции этого объекта. Когда std::optional - хороший выбор для использования, и как он компенсирует то, что не было найдено в предыдущем стандарте (C++ 11).Как использовать std :: optional?

+18

[boost.optional docs] (http://www.boost.org/doc/libs/1_53_0/libs/optional/doc/html/index.html#optional.motivation) может пролить некоторый свет. – juanchopanza

+0

Кажется, что std :: unique_ptr может, как правило, обслуживать одни и те же варианты использования. Я предполагаю, что если у вас есть что-то против нового, то опционально может быть предпочтительным, но мне кажется, что (разработчики | приложения) с таким видом на новые находятся в небольшом меньшинстве ... AFAICT, необязательно, НЕ отличная идея. По крайней мере, большинство из нас могли спокойно жить без него. Лично я был бы более комфортным в мире, где мне не нужно выбирать между unique_ptr и опциональным. Назовите меня сумасшедшим, но Zen of Python прав: пусть будет один правильный способ что-то сделать! – allyourcode

+16

Мы не всегда хотим выделить что-то в куче, которая должна быть удалена, поэтому unique_ptr не заменяет необязательный. – Krum

ответ

135

Простейший пример, который я могу думать:

std::optional<int> try_parse_int(std::string s) 
{ 
    //try to parse an int from the given string, 
    //and return "nothing" if you fail 
} 

То же самое может быть достигнуто с эталонным аргументом вместо (как показано в следующей подписи), но с использованием std::optional делает подпись и использование приятнее.

bool try_parse_int(std::string s, int& i); 

Другим способом, что это может быть сделано особенно плохо:

int* try_parse_int(std::string s); //return nullptr if fail 

Это требует динамического выделения памяти, заботясь о собственности и т.д. - всегда предпочитает один из двух других подписей выше.


Другой пример:

class Contact 
{ 
    std::optional<std::string> home_phone; 
    std::optional<std::string> work_phone; 
    std::optional<std::string> mobile_phone; 
}; 

Это крайне желательно иметь что-то вместо того, чтобы, как в std::unique_ptr<std::string> для каждого номера телефона! std::optional дает вам данные, которые отлично подходят для производительности.


Другого пример:

template<typename Key, typename Value> 
class Lookup 
{ 
    std::optional<Value> get(Key key); 
}; 

Если поиск не имеет определенный ключа в нем, то мы не можем просто вернуть «никакой ценности.»

я могу использовать его как это:

Lookup<std::string, std::string> location_lookup; 
std::string location = location_lookup.get("waldo").value_or("unknown"); 

Другой пример:

std::vector<std::pair<std::string, double>> search(
    std::string query, 
    std::optional<int> max_count, 
    std::optional<double> min_match_score); 

Это делает намного больше смысла, чем, скажем, имея четыре перегруженные функции, которые принимают все возможные комбинации max_count (или нет) и min_match_score (или нет)!

Он также устраняетпроклят «Pass -1 для max_count, если вы не хотите предел» или «Pass std::numeric_limits<double>::min() для min_match_score, если вы не хотите, минимальное количество баллов!»


Другой пример:

std::optional<int> find_in_string(std::string s, std::string query); 

Если строка запроса не s, я не хочу «не int» - не независимо от особого значения кто-то решил использовать для этой цели (- 1?).


Дополнительные примеры можно посмотреть на boost::optionaldocumentation. boost::optional и std::optional будут в основном идентичными с точки зрения поведения и использования.

+1

@ChrisCM: Это будет C способ сделать это. Путь C++ использовал бы умный указатель, чтобы избежать утечки памяти, или возвратил один из результатов через параметр handle. –

+0

Какова производительность std :: optional по сравнению с int? – gnzlbg

+8

@gnzlbg 'std :: optional ' это просто 'T' и' bool'. Реализации функций-членов чрезвычайно просты. Производительность не должна быть проблемой при ее использовании - бывают случаи, когда что-то необязательно, и в этом случае это часто является правильным инструментом для работы. –

28

Пример цитировал New adopted paper: N3672, std::optional:

optional<int> str2int(string); // converts int to string if possible 

int get_int_from_user() 
{ 
    string s; 

    for (;;) { 
     cin >> s; 
     optional<int> o = str2int(s); // 'o' may or may not contain an int 
     if (o) {      // does optional contain a value? 
      return *o;     // use the value 
     } 
    } 
} 
+0

И это считается хорошим примером? (видя, как это взято прямо из раздела обзора N3672). Как это дерьмо улучшается по сравнению с некоторым вариантом int i; while (! (cin >> i)) ;? Недоумение. – Wiz

+8

Потому что вы можете _pass_ получить информацию о том, есть ли у вас иерархия «int» или не вверх или вниз, вместо того, чтобы передавать какое-то «фантомное» значение «предполагается» иметь значение «ошибка». –

+1

@Wiz Это на самом деле отличный пример. Он (A) позволяет 'str2int()' реализовать преобразование, но он хочет, (B), независимо от метода получения 'string s', и (C) передает полный смысл через' optional ' вместо какой-то глупой магии -umber, 'bool'/reference или dynamic-allocation, основанный на его использовании. –

9

, но я не понимаю, когда я должен использовать его или как я должен использовать его.

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

class YourBlock { /* block header, format, whatever else */ }; 

std::optional<YourBlock> cache_and_get_block(
    some_socket_object& socket); 

Если прилагаемые данные завершены оформленной блок, вы можете обработать его; в противном случае, продолжайте читать и добавления данных:

void your_client_code(some_socket_object& socket) 
{ 
    char raw_data[1024]; // max 1024 bytes of raw data (for example) 
    while(socket.read(raw_data, 1024)) 
    { 
     if(auto block = cache_and_get_block(raw_data)) 
     { 
      // process *block here 
      // then return or break 
     } 
     // else [ no error; just keep reading and appending ] 
    } 
} 

Edit: относительно остальных ваших вопросов:

Когда это станд :: опциональный хороший выбор для использования

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

  • Если вы хотите, чтобы убедиться, что код клиента имеет проверить выходное значение (кто пишет код клиента не может проверить наличие ошибок - если вы пытаетесь использовать не-инициализируется указатель вы получаете дамп, если вы пытаетесь использовать un-initialized std :: optional, вы получаете исключение, исключающее блокировку).

[...] и как это компенсировать то, что не было найдено в предыдущем стандарте (C++ 11).

Для того, чтобы перейти на C++ 11, вам пришлось использовать другой интерфейс для «функций, которые могут не возвращать значение» - либо вернуть указатель, либо проверить значение NULL, либо принять выходной параметр и вернуть ошибку/Код результата для "недоступен".

Оба налагают дополнительные усилия и внимание со стороны исполнителей-клиентов, чтобы получить их право, и оба являются источником путаницы (первое нажатие на клиента-исполнителя, чтобы думать о операции как о распределении и требовать, чтобы клиентский код реализовал логику обработки указателя а второй разрешающий код клиента уйти с использованием недопустимых/неинициализированных значений).

std::optional прекрасно заботится о проблемах, связанных с предыдущими решениями.

+0

Я знаю, что они в основном одинаковы, но почему вы используете 'boost ::' вместо 'std ::'? – 0x499602D2

+4

Спасибо - я исправил его (я использовал 'boost :: optional', потому что примерно через два года его использования он жестко закодирован в моей передлоновой коре). – utnapistim

+1

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

4

Я часто использую опции для представления необязательных данных, извлеченных из файлов конфигурации, то есть, где эти данные (например, с ожидаемым, но необязательным элементом внутри XML-документа) предоставляется необязательно, так что я могу явно и ясно показать, действительно ли данные были представлены в документе XML. Особенно, когда данные могут иметь «не установленное» состояние по сравнению с «пустым» и «установленным» состоянием (нечеткой логикой). С опциональным, установленным и не установленным ясно, также пустое было бы ясным со значением 0 или null.

Это может показать, как значение «не установлено» не эквивалентно «пустому». По идее, указатель на int (int * p) может показать это, где null (p == 0) не задано, значение 0 (* p == 0) установлено и пустое, а любое другое значение (* p <> 0) устанавливается в значение.

Для практического примера у меня есть фрагмент геометрии, вытащенный из документа XML, который имеет значение под названием флагов визуализации, где геометрия может либо переопределить флаги рендеринга (установить), либо отключить флаги рендеринга (установить в 0) , или просто не влияют на флаги рендеринга (не заданы), необязательным будет ясный способ представить это.

Очевидно, что указатель на int в этом примере может достичь цели или, лучше, указателя общего доступа, поскольку он может предложить более чистую реализацию, однако я бы сказал, что в этом случае речь идет о ясности кода. Является ли нуль всегда «не установленным»? С указателем это не ясно, поскольку нуль буквально означает, что не выделено или создано, хотя оно может, но может необязательно означает «не установлено». Стоит отметить, что указатель должен быть выпущен, и в хорошей практике, установленном в 0, однако, как с общим указателем, необязательный не требует явной очистки, поэтому нет необходимости смешивать очистку с необязательно не установлен.

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

Использование указателя для представления этого потребует перегрузки концепции указателя. Чтобы представить «null» как «not set», обычно вы можете увидеть один или несколько комментариев с помощью кода, чтобы объяснить это намерение. Однако это не плохое решение, а не факультативное, но я всегда предпочитаю неявную реализацию, а не явные комментарии, поскольку комментарии не подлежат исполнению (например, путем компиляции). Примеры этих неявных элементов для разработки (те статьи в разработке, которые предоставляются исключительно для обеспечения намерения) включают различные стили стиля C++, «const» (особенно для функций-членов) и тип «bool», чтобы назвать несколько. Возможно, вам действительно не нужны эти функции кода, если все подчиняются намерениям или комментариям.

Смежные вопросы