C++
std :: optional
サーチ…
前書き
オプション(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
が定義されています。人がペットを持つことは可能ですが、必要ではありません。したがって、 Person
のpet
メンバーは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)
を呼び出すと、この事実を示すゼロが返されることがあります。
- たとえば、接続されていない2つの頂点上の関数
- 値は、返された値が意味を持つことを示すために
bool
とペアになっています。- たとえば、整数ではない文字列引数を持つ関数
std::pair<int, bool> parse(const std::string &str)
を呼び出すと、未定義のint
とbool
がfalse
設定されたペアが返され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のオプションを取って、実際に値が必要なときにデフォルトの動作を与えることができます。このようにすることで、「デフォルト動作」の決定は、あるエンジンの邪魔にならないようにデフォルト値を生成するのではなく、最も適切で直ちに必要な箇所に戻すことができます。