Подтвердить что ты не робот

Как использовать std:: optional?

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

4b9b3361

Ответ 1

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

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, я хочу "no int" - not любое специальное значение, которое кто-то решил использовать для этой цели (-1?).


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

Ответ 2

Приведен пример из Новый принятый документ: 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
         }
     }
}

Ответ 3

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

Учитывайте, когда вы пишете 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:: optional - хороший выбор для использования

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

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

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

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

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

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

Ответ 4

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

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

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

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

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

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