サーチ…


前書き

オプション(Maybe型とも呼ばれます)は、コンテンツが存在する場合と存在しない場合がある型を表すために使用されます。これらは、 std::optionalクラスとして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>

別の一般的なイディオムは、要素の1つが値が意味を持つかどうかを示すboolであるペアを提供することです。

これは、エラーの場合にはデフォルトで構成可能な値型に依存しますが、これはいくつかの型では不可能であり、他の型に対しては可能ですが望ましくありません。 optional<T> 、エラーの場合、何も構築する必要はありません。

オプションの使用による値の欠如の表現

C ++ 17より前では、 nullptr値を持つポインタを持つことは一般的に値が存在しないことを表していました。これは、動的に割り当てられ、ポインタですでに管理されている大きなオブジェクトの場合は、優れたソリューションです。しかし、ポインタで動的に割り当てられたり管理されることはめったにないintような小さな型またはプリミティブ型では、この解決法はうまく機能しません。 std::optionalは、この共通の問題に実行可能な解決策を提供します。

この例では、 struct Personが定義されています。人がペットを持つことは可能ですが、必要ではありません。したがって、 Personpetメンバーは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を返します。
    • これは、動的に割り当てられたオブジェクト、またはポインタによって管理され、大規模で管理されているオブジェクトに対しては良い解決策ですが、通常はスタックで割り当てられ、コピーによって渡される小さなオブジェクトには適していません。
  • 戻り値の型の特定の値は、失敗を示すために予約されていました。
    • たとえば、接続されていない2つの頂点上の関数unsigned shortest_path_distance(Vertex a, Vertex b)を呼び出すと、この事実を示すゼロが返されることがあります。
  • 値は、返された値が意味を持つことを示すためにboolとペアになっています。
    • たとえば、整数ではない文字列引数を持つ関数std::pair<int, bool> parse(const std::string &str)を呼び出すと、未定義のintboolfalse設定されたペアが返されfalse

この例では、2人のペットFluffyとFurballがJohnに与えられています。関数Person::pet_with_name()を呼び出して、Johnのペットウィスカーを取得します。 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はオプションで格納されている値を返します。そこにストアがない場合は引数を返します。

これにより、多分nullのオプションを取って、実際に値が必要なときにデフォルトの動作を与えることができます。このようにすることで、「デフォルト動作」の決定は、あるエンジンの邪魔にならないようにデフォルト値を生成するのではなく、最も適切で直ちに必要な箇所に戻すことができます。



Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow