Поиск…


Вступление

Опционы (также известные как типы Maybe) используются для представления типа, содержимое которого может быть или не быть. Они реализованы на C ++ 17 в качестве std::optional . Например, объект типа std::optional<int> может содержать некоторое значение типа int или не может содержать никакого значения.

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

Другие подходы к выбору

Есть много других подходов к решению проблемы, что std::optional решает, но ни один из них не является полным: использование указателя, использование часового, или использование pair<bool, T> .

Необязательный против указателя

В некоторых случаях мы можем предоставить указатель на существующий объект или nullptr для указания отказа. Но это ограничивается теми случаями, когда объекты уже существуют - optional , как тип значения, также может использоваться для возврата новых объектов, не прибегая к распределению памяти.

Необязательный против Sentinel

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

Необязательный vs std::pair<bool, T>

Другой распространенной идиомой является предоставление пары, где один из элементов - это bool указывающий, имеет ли значение значение.

Это зависит от типа значения, являющегося конструктивным по умолчанию в случае ошибки, что невозможно для некоторых типов и возможно, но нежелательно для других. optional<T> , в случае ошибки, не нужно ничего строить.

Использование опций для представления отсутствия значения

До C ++ 17, имея указатели со значением nullptr обычно представляло отсутствие значения. Это хорошее решение для больших объектов, которые были динамически распределены и уже управляются указателями. Однако это решение не работает хорошо для небольших или примитивных типов, таких как int , которые редко когда-либо динамически распределяются или управляются указателями. std::optional обеспечивает жизнеспособное решение этой общей проблемы.

В этом примере определяется struct Person . Человек может иметь домашнее животное, но не обязательно. Таким образом, pet член Person объявлен с std::optional оберткой.

#include <iostream>
#include <optional>
#include <string>

struct Animal {
    std::string name;
};

struct Person {
    std::string name;
    std::optional<Animal> pet;
};

int main() {
    Person person;
    person.name = "John";

    if (person.pet) {
        std::cout << person.name << "'s pet's name is " <<
            person.pet->name << std::endl;
    }
    else {
        std::cout << person.name << " is alone." << std::endl;
    }
}

Использование опций для представления отказа функции

До C ++ 17 функция обычно представляла отказ одним из нескольких способов:

  • Был возвращен нулевой указатель.
    • например, вызов функции Delegate *App::get_delegate() в экземпляре App , у которого не было делегата, вернет nullptr .
    • Это хорошее решение для объектов, которые были динамически распределены или являются большими и управляются указателями, но не являются хорошим решением для небольших объектов, которые обычно распределяются по стеклу и передаются путем копирования.
  • Определенное значение типа возврата зарезервировано для указания отказа.
    • например, вызов функции unsigned shortest_path_distance(Vertex a, Vertex b) на две вершины, которые не связаны, может возвращать ноль, чтобы указать этот факт.
  • Значение было сопряжено вместе с bool чтобы указать, что возвращаемое значение имеет смысл.
    • например, вызов функции std::pair<int, bool> parse(const std::string &str) со строковым аргументом, который не является целым числом, возвратит пару с неопределенным int и bool установленным в false .

В этом примере Джону даются два питомца: «Пушистый» и «Furball». Затем Person::pet_with_name() функция Person::pet_with_name() для извлечения любимых Person::pet_with_name() Джона. Поскольку у John нет домашнего животного с именем Whiskers, функция не работает, и вместо этого возвращается std::nullopt .

#include <iostream>
#include <optional>
#include <string>
#include <vector>

struct Animal {
    std::string name;
};

struct Person {
    std::string name;
    std::vector<Animal> pets;
    
    std::optional<Animal> pet_with_name(const std::string &name) {
        for (const Animal &pet : pets) {
            if (pet.name == name) {
                return pet;
            }
        }
        return std::nullopt;
    }
};

int main() {
    Person john;
    john.name = "John";
    
    Animal fluffy;
    fluffy.name = "Fluffy";
    john.pets.push_back(fluffy);
    
    Animal furball;
    furball.name = "Furball";
    john.pets.push_back(furball);
    
    std::optional<Animal> whiskers = john.pet_with_name("Whiskers");
    if (whiskers) {
        std::cout << "John has a pet named Whiskers." << std::endl;
    }
    else {
        std::cout << "Whiskers must not belong to John." << std::endl;
    }
}

необязательно как возвращаемое значение

std::optional<float> divide(float a, float b) {
  if (b!=0.f) return a/b;
  return {};
}

Здесь мы возвращаем либо дробь a/b , но если она не определена (была бы бесконечной), мы вместо этого возвращаем пустой необязательный.

Более сложный случай:

template<class Range, class Pred>
auto find_if( Range&& r, Pred&& p ) {
  using std::begin; using std::end;
  auto b = begin(r), e = end(r);
  auto r = std::find_if(b, e , p );
  using iterator = decltype(r);
  if (r==e)
    return std::optional<iterator>();
  return std::optional<iterator>(r);
}
template<class Range, class T>
auto find( Range&& r, T const& t ) {
  return find_if( std::forward<Range>(r), [&t](auto&& x){return x==t;} );
}

find( some_range, 7 ) ищет контейнер или диапазон some_range для чего-то равного числу 7 . find_if делает это с предикатом.

Он возвращает либо пустой необязательный, если он не был найден, либо необязательный, содержащий итератор для элемента, если он был.

Это позволяет:

if (find( vec, 7 )) {
  // code
}

или даже

if (auto oit = find( vec, 7 )) {
  vec.erase(*oit);
}

без необходимости возиться с итераторами начала и окончания.

value_or

void print_name( std::ostream& os, std::optional<std::string> const& name ) {
  std::cout "Name is: " << name.value_or("<name missing>") << '\n';
}

value_or либо возвращает значение, хранящееся в необязательном, либо аргумент, если там ничего нет.

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



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow