Как следует использовать std::опционально?
Я читаю документацию std::experimental::optional
и у меня есть хорошая идея о том, что он делает, но я не понимаю , когда Я должен использовать его, или, как я должен использовать его. На сайте пока нет примеров, которые затрудняют мне понимание истинной концепции этого объекта. Когда std::optional
хороший выбор для использования, и как он компенсирует то, что не было найдено в предыдущем стандарте (C++11).
4 ответов
самый простой пример, который я могу придумать:
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
(или нет)!
кроме исключает the проклят "передать -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::optional
документация. boost::optional
и std::optional
в основном будет идентичным с точки зрения поведения и использования.
пример приведен из новая принятая бумага: 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
}
}
}
но я не понимаю, когда я должен использовать его или как я должен использовать его.
рассмотрим, когда вы пишете 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: относительно остальных ваших вопросов:
когда std:: необязательно хороший выбор для использования
когда вы вычисляете значение и должны его вернуть, это делает лучшую семантику для возврата по значению, чем для ссылки на выходное значение (которое может не быть сгенерировано).
когда вы хотите гарантировать, что клиентский код и для проверки выходного значения (кто бы запись клиентского кода может не проверяться на наличие ошибки - при попытке использовать неинициализированный указатель вы получаете дамп ядра; при попытке использовать неинициализированный std:: необязательно, вы получаете исключение catch-able).
[...] и как он компенсирует то, что не было найдено в предыдущем стандарте (C++11).
до C++11 вы должны были использовать другой интерфейс для "функций, которые могут не возвращать значение" - либо возвращать указатель и проверьте значение NULL или примите выходной параметр и верните код ошибки/результата для "недоступно".
оба накладывают дополнительные усилия и внимание со стороны клиентского реализатора, чтобы получить его правильно, и оба являются источником путаницы (первый толкает клиентского реализатора думать об операции как о выделении и требует, чтобы клиентский код реализовывал логику обработки указателей, а второй позволяет клиентскому коду уйти с использованием недопустимых/неинициализированных значений).
std::optional
красиво заботится о проблемах, возникающих с предыдущими решениями.
Я часто использую optionals для представления необязательных данных, извлеченных из файлов конфигурации, то есть где эти данные (например, с ожидаемым, но не необходимым элементом в XML-документе) необязательно предоставляются, так что я могу явно и четко показать, действительно ли данные присутствовали в XML-документе. Особенно, когда данные могут иметь состояние "не установлено", а не "пустое" и "заданное" состояние (нечеткая логика). С необязательным, set и not set ясно, также пустой будет ясно со значением 0 или null.
Это может показать, как значение "не установлено" не эквивалентно "пустой". В концепции указатель на int (int * p) может показать это, где null (p == 0) не установлен, значение 0 (*p == 0) установлено и пусто, а любое другое значение (*p 0) установлено в значение.
для практического примера у меня есть кусок геометрии, извлеченный из XML-документа, который имел значение, называемое флагами рендеринга, где геометрия может либо переопределить флаги рендеринга (set), отключите флаги рендеринга (установлено значение 0) или просто не повлияйте на флаги рендеринга (не установлено), необязательный способ представить это.
ясно, что указатель на int в этом примере может достичь цели или, лучше, указатель на общий ресурс, поскольку он может предложить более чистую реализацию, однако я бы сказал, что в этом случае речь идет о ясности кода. Всегда ли null является"не установленным"? С указателем непонятно, так как null буквально означает не выделенный или созданный, хотя он мог бы, еще может Не обязательно означает "не установлено". Стоит отметить, что указатель должен быть освобожден, и в хорошей практике установлено значение 0, однако, как и с общим указателем, необязательный не требует явной очистки, поэтому нет необходимости смешивать очистку с необязательным, не установленным.
Я считаю, что речь идет о ясности кода. Ясность снижает стоимость обслуживания и разработки кода. Четкое понимание намерения кода это невероятно ценно.
использование указателя для представления этого потребует перегрузки концепции указателя. Чтобы представить " null "как" not set", обычно вы можете увидеть один или несколько комментариев через код, чтобы объяснить это намерение. Это неплохое решение вместо необязательного, однако я всегда выбираю неявную реализацию, а не явные комментарии, поскольку комментарии не подлежат принудительному исполнению (например, путем компиляции). Примеры этих неявных элементов для разработки (те статьи в разработке, которые предоставляются исключительно для обеспечения намерения) включают различные приведения стиля C++, " const "(особенно для функций-членов) и тип" bool", чтобы назвать несколько. Возможно, вам действительно не нужны эти функции кода, пока все подчиняются намерениям или комментариям.