std::optional<T&> とオプションの参照について話しましょう

これは私の比較シリーズの第 2 部であるはずでしたが、ほぼ完成しましたが、大学のもののために、それを磨く時間がありませんでした.

しかし、オプションのディスカッションが再び始まったので、本当にしたかっただけです このトピックに関する私の生の考えをすぐに共有してください。運が良くて、私の言いたいことがわからない場合:03 代入の振る舞いが (実際にはそうであるにもかかわらず) 明確ではなかったため、現在はコンパイルできません。基本的に、私が答えたい議論には次の 4 つの質問があります。

<オール>
  • 13 です ポインタと同じですか?
  • 21 は必要ですか ?
  • 代入演算子は再バインドまたは代入スルーのどちらを使用する必要がありますか?
  • 代入演算子も必要ですか?
  • tl;dr:いいえ、しません、再バインドします、いいえ。

    1. 38 です ポインタと同じ?

    「オプションの43」を持つことは何を意味しますか 」?まあ、それは 58 です 60 の場合もあります .

    ポインタ、73 ?

    いいえ、そうではありません。

    89 の間にはもっと重要な違いがあります と 93 nullability に加えて:A 100 暗黙の作成とアクセス、111 があります。 明示的な作成とアクセス。

    オブジェクトがある場合は、それへの参照をサイレントにバインドできます。また、参照がある場合は、それを あたかも 扱うことができます。 それはオブジェクトでした。ポインターの場合は、明示的に 126 を使用する必要があります と 133 .

    そして、この違いは非常に大きいです:それは 143 を意味します 追加の構文の問題なしで関数パラメーターに使用できます:

    void print(const T& obj);
    …
    T obj = …;
    print(obj);
    

    152 は使いたくないでしょう 呼び出し側は余分な作業を行う必要があるため、不要な 165 を使用する必要があります .これは厄介です。

    したがって、当然のことながら、オプションの引数が必要な場合は、同じ理由でポインターを使用したくないでしょう。なぜ今、不要な構文オーバーヘッドを導入するのでしょうか?それは呼び出し元にとって重要ではありません。

    だから 175 189 と同じではありません :明示的ではなく、暗黙的な作成構文があります。

    193 持つことはできませんが、暗黙的なアクセスです。現在実装できないだけでなく、基本的に不可能です:208 の場合 暗黙的なアクセス構文を持つために、それに対するすべての操作は参照オブジェクトに委任されます.これには、オブジェクトを参照しているかどうかのチェックが含まれます!Any 219 または 228 これは、明示的な構文が必要であることを意味します。それ以外の場合は、オプションに null のオブジェクトがあるかどうかを確認するだけです。

    より徹底的な分析は、今年初めに開催された C++Now での Rethinking Pointers の講演の最初の 20 分間で見つけることができます。

    2. 231 は必要ですか ?

    241 として 254 と同じではありません 、 268 を使用する状況を調べる必要があります そこにオプションのバージョンが必要かどうかを考えてください。

    幸いなことに、私は Rethinking Pointers の講演でまさにそれを行いました。

    関数パラメータ

    void print(const T& obj);
    void sort(Container& cont);
    

    ここでは、コピーを避けるか、引数をインプレースで変更したいと考えています。オプションの引数が必要な場合は、278 が解決策です。ただし、単純に関数をオーバーロードすることも同様に機能します。

    ゲッター関数

    const std::string& person::name() const;
    

    繰り返しますが、コピーは避けたいと考えています。

    戻り値が利用できない場合は、非参照 280 を使用できます 、しかし追加のコピーを支払う必要があります。または、連絡先を絞り込んで、オブジェクトがそこにあることを必要とする前提条件を追加することもできますが、これはタイプセーフではありません.

    LValue 関数

    T& std::vector::operator[](std::size_t index);
    T& std::optional<T>::value();
    

    ここでは絶対に 戻り値の型として左辺値が必要です。これが参照の背後にある動機であるため、それらを使用します。ただし、オプションの参照は機能しません。演算子の従来の使用法と互換性のない暗黙的なアクセスが失われます。

    範囲ベースの 292 ループ

    for (auto& cur : container)
      …
    

    ここではオプションの参照は必要ありません。

    関数呼び出し時の寿命延長 (エキスパートのみ):

    const std::string& name = p.name();
    // use `name` multiple times
    

    ライフタイム延長は、通常の参照でのみ機能します。

    301 を使用する必要がある状況は以上です。 .317 を使用できる唯一の状況 コピーを避けたい関数パラメータと getter です。これはそれほど説得力のある使用例ではありません。

    3.代入演算子は再バインドまたは代入スルーする必要がありますか?

    課題根本的に はコピーの最適化です。「現在のオブジェクトを破棄する」および「新しいオブジェクトをコピーする」と同じことを行うだけです。

    320 と書くと 、それは 333 を変更します 347 のコピーです .これはすべての 351 に当てはまります 、360 を含む :If 378 384 への参照です 、次に 396 406 への参照にもなります 、 415 への参照であったとしても そのため、コピー代入演算子は再バインド操作を行います。

    426 434 を取る代入演算子もあります :この代入演算子は、442 を取るコンストラクターの最適化です .

    そのため、現在のオブジェクトが存在する場合は破棄し、その中に新しいオブジェクトを作成します。ただし、これは最適化であるため、454 を使用します。 オプションにすでに値がある場合。 469 の代入演算子 「破壊」に続いて「構築」するよりも効率的かもしれません。

    ただし、472 の代入演算子が コピーの最適化です! 482 を指定すると どこで 495 「ロケットを発射する」を意味しますが、これは失敗します。しかし、これはオプションのせいではありません。あなたのタイプは愚かです!

    そのような愚かなタイプの 1 つが 506 です。 :516の代入演算子 「destroy」に続く「copy」の最適化ではありません。これは、参照に代入演算子がないためです。 :参照に対して行うすべての操作は、実際にはそれが参照するオブジェクトに対して行われます.これには代入が含まれるため、代入演算子は値を代入します.

    今では、525 でその動作をしていると考える人もいます。 539

    そうではありません。

    他の反論を無視すると、これらのセマンティクスは 542 として混乱を招きます 551 の状態に応じて、まったく異なることを行います !

    std::optional<T&> opt = …;
    
    T obj;
    opt = obj;
    // if opt was empty before, it will now refer to obj
    // if opt wasn't empty before, it will now refer to an object with the same value as obj
    
    return opt; // so this is legal only if the optional wasn't empty before
    

    代入演算子はこのように振る舞うべきではないため、このように振る舞う代入演算子の前例はありません。

    4.代入演算子も必要ですか?

    568 を使用するときはいつでも 参照自体を変更する必要はありません – 結局、できません。 585599 を変更する必要はありません

    今度は 604 の「アサインスルー」の人々 この動作は 615 と一致していると主張します .

    参照は割り当てられないため、そうではありません。

    はい、628 を書いています コンパイル ですが、割り当てではありません。すべての理由でのみ機能します 参照に対して行われる操作は、それが参照するオブジェクトに対して行われます。

    前に言ったように、null 許容参照がある場合、それはできません。なぜなら、null 許容性をチェックする構文がないからです。したがって、630 と完全に一致する唯一の方法は、 644 の場合 656 を持つべきではありません。 、664 関数など。結局、671 は不変なので、686

    691 を変異させる必要がある場合 、あなたは欲しくなかった 701 、ポインターが必要でした。なぜなら、オプションを永続的な場所に保存し、それを明確にするために明示的な作成構文を使用する必要があったからです。それについては、私の話で詳しく説明します。

    716 がある場合は注意してください 修飾子がないと、722 のようには動作しません。 – 736だから 743 のように振る舞わない .ジェネリック コードが 754 を処理できないのと同じように 、 769 も処理しません .

    したがって、「optional 774」と綴るべきではありません。 」として 786 795 と呼ぶべきだと思います。

    結論

    私の意見では、808 は必要ありません .これは、使用例がほとんどない奇妙なタイプです。

    委員会が 818 の追加を決定した場合 努力する価値があります。不変の 822 でなければなりません 836 の実際の使用例については 、 841 の使用例と同じように 、それは実際には問題ではありません。

    850 のように振る舞う型に注意してください。 ですが、そうではありませんが、役に立ちます:A 869 はさまざまなことを行うことができるので、それが行うことの 1 つだけを明示的にモデル化する個別の型を追加することをお勧めします。たとえば、私の type_safe ライブラリには 871 があります。 、これは 884 のようなものです nullable 897 とは異なります ただし、901 のように綴ってはいけません。 919 ではないため .

    詳細については、私の Rethinking Pointers の講演をご覧ください。