どのようにstd :: optionalを使用する必要がありますか?


133

私はのドキュメントを読んstd::experimental::optionalでいて、それが何をするかについては良い考えを持っていますが、いつ理解するのかわかりませんそれを使用する必要があるのか​​、またはどのように使用するのか。このサイトにはまだ例が含まれていないため、このオブジェクトの真の概念を理解するのは困難です。いつstd::optional使用するのが適切であり、以前の標準(C ++ 11)で見つからなかったものをどのように補正するか。


19
boost.optionalドキュメントは、いくつかの光を当てることがあります。
juanchopanza 2013年

std :: unique_ptrは一般的に同じユースケースに対応できるようです。あなたがnewに対して何かを持っているなら、それからオプションが好ましいかもしれませんが、newに対するその種の見方を持つ(開発者|アプリ)は少数派であると私には思われます... AFAICT、オプションは素晴らしいアイデアではありません。少なくとも、ほとんどの人はそれなしで快適に生活できました。個人的には、unique_ptrとオプションのどちらかを選択する必要がない世界のほうが快適です。クレイジーと呼んでも、Zen of Pythonは正しい:何かを行うための正しい方法が1つあるようにしましょう!
allyourcode

19
削除する必要があるものを常にヒープに割り当てる必要はないので、unique_ptrはオプションの代わりにはなりません。
クルム

5
@allyourcodeポインタはの代わりにはなりませんoptional。あなたがしたい想像しoptional<int>たり<char>。動的に割り当て、逆参照、次に削除する必要があるのは本当に「Zen」だと思いますか。そうでなければ割り当てを必要とせず、スタックにコンパクトに収まり、さらにはレジスタに収まるようなものでしょうか。
underscore_d

1
含まれている値のスタックとヒープの割り当てから疑いなく生じるもう1つの違いは、テンプレートの引数がstd::optional(の場合は可能ですがstd::unique_ptr)不完全な型になることはないということです。より正確には、規格はT [...]がDestructibleの要件を満たさなければならないことを要求しています
dan_din_pantelimon 2018年

回答:


172

私が考えることができる最も単純な例:

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

これには、動的メモリ割り当て、所有権の心配などが必要です。常に、上記の他の2つのシグネチャのいずれかを優先してください。


もう一つの例:

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(またはそうでない)のすべての可能な組み合わせをとる4つの関数オーバーロードを持つよりもはるかに理にかなっています!

また、排除呪わ「合格-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基本的に行動し、使用量の面で同じになります。


13
@gnzlbg std::optional<T>は単なるa Tとa boolです。メンバー関数の実装は非常に簡単です。パフォーマンスは、それを使用するときに本当に心配する必要はありません-何かがオプションである場合があります。その場合、これは多くの場合、ジョブに適したツールです。
ティモシーシールド2013年

8
@TimothyShields std::optional<T>はそれよりもはるかに複雑です。配置newなど、適切な配置とサイズを使用して、文字列型(つまりで使用constexpr)にするために使用されます。素朴Tboolアプローチはすぐに失敗します。
Rapptz 2014年

15
@Rapptz 256行目:union storage_t { unsigned char dummy_; T value_; ... }289行目:「a and a 」struct optional_base { bool init_; storage_t<T> storage_; ... }はないのはなぜですか?実装は非常にトリッキーで簡単なものではありませんが、概念的および具体的にはタイプはa とa です。「素朴でアプローチはすぐに失敗するでしょう。」コードを見たときに、どのようにこのステートメントを作成できますか?TboolTboolTbool
ティモシーシールズ

12
@Rapptzそれはまだブールとintのスペースを保存しています。共用体は、それが実際に必要でない場合にオプションのTを作成しないようにするためだけにあります。それでもstruct{bool,maybe_t<T>}、組合はstruct{bool,T}すべての場合にTを構築することを行わないために存在します。
PeterT 2014年

12
@allyourcode非常に良い質問です。両方std::unique_ptr<T>std::optional<T>いくつかの意味での役割果たすん「オプションをT。」それらの違いを「実装の詳細」として説明します。追加の割り当て、メモリ管理、データの局所性、移動のコストなどです。std::unique_ptr<int> try_parse_int(std::string s);たとえば、理由がなくてもすべての呼び出しに割り当てが発生するため、 。私はクラスを決して持ってstd::unique_ptr<double> limit;いません-なぜ割り当てを行ってデータの局所性を失うのですか?
ティモシーシールド

35

新しく採用されたペーパー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
         }
     }
}

13
「エラー」の意味を持つ「想定」された「ファントム」値を渡すのではなく、呼び出し階層を取得したかどうかの情報を渡すことができるためですint
Luis Machuca 2013年

1
@Wizこれは実際には素晴らしい例です。(A)必要に応じstr2int()て変換を実装できます。(B)を取得する方法に関係なくstring s、(C)optional<int>いくつかの愚かな魔法番号、bool/参照、または動的割り当てベースの方法の代わりに完全な意味を伝えます。している。
underscore_d 2017年

10

いつ使うべきか、どう使うべきかわかりません。

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が適切な場合

  • 値を計算してそれを返す必要がある場合、(生成されない可能性がある)出力値への参照を取得するよりも、値で返すほうがセマンティクスが向上します。

  • クライアントコード出力値をチェックする必要があることを確認したい場合(クライアントコードを書き込んだ人はエラーをチェックしない可能性があります-初期化されていないポインターを使用しようとすると、コアダンプが発生します。初期化されたstd :: optional、キャッチ可能な例外が発生します)。

[...]そして、以前の標準(C ++ 11)で見つからなかったものをどのように補正しますか?

C ++ 11以前では、「値を返さない可能性のある関数」には別のインターフェースを使用する必要がありました-ポインターで返してNULLを確認するか、出力パラメーターを受け入れて「使用不可」のエラー/結果コードを返します」

どちらもクライアントインプリメンターからそれを正しくするために余分な労力と注意を課し、どちらも混乱の原因です(最初にクライアントインプリメンターに操作を割り当てとして考えるようにプッシュし、クライアントコードにポインター処理ロジックの実装を要求し、2番目は許可する無効/初期化されていない値の使用を回避するクライアントコード)。

std::optional 以前のソリューションで発生する問題をうまく処理します。


基本的に同じだと知っていますが、boost::代わりにを使用しているのはなぜstd::ですか?
0x499602D2 2013年

4
ありがとう-私はそれを修正しました(boost::optional約2年間使用した後、前頭前野にハードコードされているので使用しました)。
utnapistim 2013年

1
複数のブロックが完了した場合、1つだけが返され、残りのブロックが破棄される可能性があるため、これは悪い例です。代わりに、関数は空の可能性があるブロックのコレクションを返す必要があります。
Ben Voigt 2013年

あなたが正しい; それは悪い例でした。「可能な場合は最初のブロックを解析する」の例を変更しました。
utnapistim 2013年

4

私はしばしばオプションを使用して、構成ファイルから引き出されたオプションのデータを表します。つまり、そのデータ(XMLドキュメント内の予想されるが必須ではない要素など)がオプションで提供される場所を示します。これにより、データは実際にはXMLドキュメントに存在していました。特に、データが「空」および「設定」状態(ファジーロジック)に対して「未設定」状態になる可能性がある場合。オプションで、セットされていることとセットされていないことがクリアである場合、値が0またはnullの場合も空になります。

これは、「未設定」の値が「空」と同等ではないことを示すことができます。概念的には、int(int * p)へのポインターはこれを示すことができ、null(p == 0)が設定されていない、値0(* p == 0)が設定されて空、その他の値(* p <> 0)は値に設定されます。

実際の例として、レンダーフラグと呼ばれる値を持つXMLドキュメントから取得されたジオメトリの一部があります。この場合、ジオメトリはレンダーフラグをオーバーライドする(設定する)か、レンダーフラグを無効にする(0に設定する)か、または単にしないことができます。レンダーフラグ(設定されていない)に影響を与える場合、オプションでこれを明確に表現できます。

明らかに、この例では、intへのポインターが目標を達成できます。より良い方法は、より明確な実装を提供できる共有ポインターですが、この場合のコードの明快さに関するものだと私は主張します。nullは常に「未設定」ですか?nullは、文字通り、割り当てられたり作成し、それはかかわらない手段としてのポインタで、それは、明確ではありませんでし、まだ可能性があります必ずしもありません「に設定していない」を意味します。ポインタを解放する必要があることを指摘する価値があります。ただし、共有ポインタの場合と同様に、オプションは明示的なクリーンアップを必要としないため、クリーンアップと混同する心配はありません。オプションが設定されていない。

私はそれがコードの明快さに関するものだと思います。明快さはコードのメンテナンスと開発のコストを削減します。コードの意図を明確に理解することは非常に貴重です。

これを表すためにポインターを使用するには、ポインターの概念をオーバーロードする必要があります。「null」を「未設定」として表すには、通常、コードを介して1つ以上のコメントを表示して、この意図を説明します。これはオプションではなく悪い解決策ではありませんが、コメントは(コンパイルなどによって)強制できないため、明示的なコメントではなく暗黙的な実装を常に選択します。開発のためのこれらの暗黙的なアイテムの例(純粋に意図を強制するために提供される開発中の記事)には、さまざまなC ++スタイルのキャスト、「const」(特にメンバー関数に関する)、「bool」タイプなどがあります。誰もが意図やコメントに従う限り、間違いなくこれらのコード機能は本当に必要ありません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.