web-dev-qa-db-ja.com

std :: optionalの使用方法

私は std::experimental::optional のドキュメントを読んでいますが、それが何をするかについてはよく知っていますが、理解できませんwhenそれを使用するか、どのように使用するか。このサイトにはまだサンプルが含まれていないため、このオブジェクトの真の概念を把握するのが難しくなっています。 std::optionalを使用するのが適切な場合と、以前の標準(C++ 11)で見つからなかったものをどのように補正するか。

116
0x499602D2

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

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つの関数オーバーロードを持つよりもはるかに理にかなっています!

また、eliminatestheaccursed "Pass -1 for max_count制限が必要ない場合」または「min_match_scoreに合格std::numeric_limits<double>::min()最小スコアが必要ない場合」!


もう一つの例:

std::optional<int> find_in_string(std::string s, std::string query);

クエリ文字列がsにない場合、「no int」が必要です-not誰かが決めた特別な値この目的で使用するには(-1?)。


その他の例については、boost::optionaldocumentation をご覧ください。 boost::optionalstd::optionalは、動作と使用方法の点で基本的に同じです。

164
Timothy Shields

新しい採択論文: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
         }
     }
}
31
taocp

しかし、私はそれをいつ使うべきか、どのように使うべきかを理解していません。

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を使用するのが適切か

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

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

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

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

両方とも、それを正しくするためにクライアント実装者に余分な労力と注意を課し、両方が混乱の原因となります(最初にクライアント実装者に操作を割り当てとして考え、クライアントコードにポインター処理ロジックを実装することを要求し、無効/初期化されていない値を使用して回避するクライアントコード)。

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

9
utnapistim

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

これにより、「not set」の値が「empty」と同等ではないことがわかります。概念的には、int(int * p)へのポインターはこれを表示できます。null(p == 0)が設定されていない場合、0(* p == 0)の値が設定されて空、およびその他の値(* p <> 0)は値に設定されます。

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

この例では、intへのポインターが目標を達成できることは明らかです。より明確な実装を提供できるので、より良いのは共有ポインターですが、この場合はコードの明快さについてです。 nullは常に「設定されていない」ですか?ポインターでは、nullは文字通り割り当ても作成もされないことを意味するため、明確ではありませんが、couldですが、必ずしも必要ではないは「設定されていない」ことを意味します。ポインタを解放する必要があることを指摘する価値があります。また、グッドプラクティスでは0に設定しますが、共有ポインタの場合のように、オプションでは明示的なクリーンアップは不要です。したがって、クリーンアップと混同する心配はありません。オプションが設定されていません。

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

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

4
Kit10