Как следует использовать std::опционально?



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

392   4  

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 если вы не хотите предел" или " пройти 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::optional, вы получите исключение с возможностью перехвата).

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

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

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

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

Я часто использую optionals для представления необязательных данных, извлеченных из файлов конфигурации, то есть там, где эти данные (например, с ожидаемым, но не необходимым элементом в XML-документе) необязательно предоставляются, так что я могу явно и четко показать, действительно ли данные присутствовали в XML-документе. Особенно, когда данные могут иметь состояние "не установлено", а не "пустое" и "установленное" состояние (нечеткая логика). С дополнительным, установить и не устанавливается понятно, тоже пусто будет ясно со значением 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", чтобы назвать несколько. Возможно, вам действительно не нужны эти функции кода, пока все подчиняются намерениям или комментариям.

Comments

    Ничего не найдено.