Как использовать 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 (или нет)!

Также устраняется проклятый "Пасс" -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<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 ]
    }
}

Отредактируйте: относительно остальных ваших вопросов:

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

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

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

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

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

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

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

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

Это может показать, что значение "not set" не эквивалентно "empty". В принципе, указатель на int (int * p) может показать это, где нулевое значение (p == 0) не установлено, значение 0 (*p == 0) установлено и пусто, и любое другое значение (*p <> 0) устанавливается в значение.

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

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

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

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

Другие вопросы по тегам