ポインターの (より良い) 分類法

C++Now 2018 で、ポインターの再考について講演しました:jonathanmueller.dev/talk/cppnow2018.

私が ACCU で行った同様の講演を見たとしても、そのバージョンの方がはるかに優れているため、チェックすることを強くお勧めします. ポインターよりも参照を使用する場合、スマートポインターなどをいつ使用するかについての一般的なガイドラインを再発見し、説明しています. /P>

専門家であれば、構造化された分析からより深い意味を得ることができます。また、初心者であれば、要約されたガイドラインを理解できます。

しかし、最も価値があるのはポインタ型の分類だと思います。それは T* について話すときに新しい語彙を与えてくれます vs std::optional<T&> これにより、議論全体に明白な答えが与えられます。

ここにも大きな問題があります。ネーミングが難しいのです。

特に、トーク中の分類学のネーミングが悪いので、新しい名前を導入しましょう。

分類法?

「ポインタ型の分類法」とはどういう意味ですか?

他のオブジェクトを参照するために使用できる型はたくさんあります:

  • T*
  • T&
  • std::unique_ptr<T>
  • gsl::non_null<T*>
  • type_safe::object_ref<T>

ガイドラインを提供する際に、考えられるすべての実装について話すのは面倒です。

それも不要です!多くのタイプは非常に似ています.

そこで、講演の中で私は彼らが持っている特性を見ました.

コア プロパティ Ⅰ:オブジェクト アクセス構文

オブジェクト アクセス構文は、実際の明白な質問に答えます:何らかのポインタが与えられた場合、それが指すオブジェクト、つまりポインティを取得するにはどうすればよいでしょうか?

いくつかのオプションがあります:

  • ドット アクセス :T& と考えてください . do_sth_with_pointee(ref) と書くだけです または ref.member .追加のフープをジャンプする必要はありません。
  • 矢印アクセス :T* と考えてください .それらを明示的に逆参照する必要があるため、do_sth_with_pointee(*ptr) または ptr->member .
  • (メンバー) 機能アクセス :ポインティを取得するには、何らかの (メンバー) 関数を呼び出す必要があるため、do_sth_with_pointee(ptr.get())
  • キャスト アクセス: static_cast<T&>(ptr) のようなことをしなければなりません ポインティを取得します。

ガイドラインの目的上、指示先を取得するために必要な正確な構文は実際には問題ではありません。重要なのは、何かが必要かどうかだけです。 指示先を取得するための特別な構文

したがって、オブジェクト アクセス構文の本当の問題は、暗黙的なアクセス構文の間です (T& だと思います ) と 明示的なアクセス構文 (T* だと思います ).

コア プロパティ Ⅱ:ポインタ作成構文

これは、オブジェクト アクセス構文の逆です:オブジェクトが与えられた場合、そのオブジェクトへのポインターを取得するにはどうすればよいですか?

オプションのリストは似ています:

  • 暗黙の作成 :T& だと思います . T& ref = obj と書くだけです 、何もする必要はありません。
  • 作成アドレス :T* だと思います . &obj を使用して明示的にポインタを作成する必要があります または std::addressof(obj) .
  • 関数の作成 :type_safe::ref(obj) のように、ポインターを取得するには何らかの関数を呼び出す必要があります .
  • 作成の機能とアドレス :gsl::non_null<T*>(&obj) のように、ポインターを渡す関数を呼び出す必要があります。 .

繰り返しになりますが、正確な構文は重要ではありません。重要なのは、構文が必要かどうかです。

ここでも、違いは 暗黙的な作成構文 です (T& だと思います ) と 明示的な作成構文 (T* だと思います ).

コア コンセプト

つまり、それぞれに 2 つの値を持つ 2 つの重要なプロパティがあります。つまり、可能な組み合わせは 4 つあります。

<オール>
  • 暗黙の作成構文 暗黙のアクセス構文 :T&
  • 暗黙の作成構文 および明示的なアクセス構文 :???
  • 明示的な作成構文 暗黙のアクセス構文 :???
  • 明示的な作成構文 および明示的なアクセス構文 :T*
  • ケース 2 に戻ります。これは、コア コンセプト 1 の特別なバージョンです。

    また、演算子のドットがないため、暗黙的なアクセス構文を使用してユーザー定義型を実際に記述することはできません。取得できる最も近いものは std::reference_wrapper です。 これには、メンバーにアクセスするためのキャストが必要であり、面倒です。メンバー関数ごとに転送関数を作成する必要があり、汎用的に行うことはできません。

    また、明示的な作成と暗黙的なアクセスを備えた組み込み型がないため、ケース 3 の実際のジェネリック型はありません。そのため、この概念に名前を付けることはしませんでした。

    残りはケース 1 と 4 です。

    暗黙の作成構文と暗黙のアクセス構文を持つ型は、私が Alias と呼んだものです その名前は妥当だと思います — T& 、たとえば、 T であるかのように動作します .

    問題は、ケース 4 の名前です。明示的な作成構文と明示的なアクセス構文で型を呼び出しました... Reference .

    はい、これは T* という意味です Reference です 一方 T& 残念ながらそうではありません。

    公平を期すために、その名前を選ぶにはいくつかの議論があります:

    • Reference を「逆参照」する必要があります アクセスする前に、Alias を「逆参照」する必要はありません .
    • Rust などの他の言語 T* のように動作する参照型を持つ 、だからモデル Reference .
    • 私の type_safe ライブラリには object_ref<T> があります T* のように動作します 、モデル Reference .
    • Pointer を使用できませんでした any を指すために「ポインター」という用語を使用したためです。 別のオブジェクトを指すことができる型、つまり Reference または Alias .

    ですから、完璧な世界では T& だと思います Alias と呼ばれる 、Reference ではありません その命名の方が自然だからです。

    悲しいことに、C++ は別の先例を設定したため、現在、名前を変更しています。

    コア コンセプトのより良い名前

    後から考えると、はるかにうまく機能する明らかな名前があります:

    暗黙の作成構文を持つ型 暗黙のアクセス構文 、つまり T& に似たもの 、参照のような型です .

    明示的な作成構文を持つ型 および明示的なアクセス構文 、つまり T* に似たもの 、ポインターのような型です .

    この命名スキームの唯一の欠点は、追加のプロパティが概念に結び付けられることを意味する可能性があることです。たとえば、ポインターは nullptr になる可能性があります。 、ただし、null 以外のポインターのような型があります (私の type_safe::object_ref<T> のように) または、ポインターでポインター演算を実行できますが、ポインターのような型では実行できない場合があります。

    ただし、これは比較的小さな欠点です。

    私の講演では、any を意味するために「ポインタのような型」を使用したことに注意してください。 他の何かを指すことができる型 (そして、別のオブジェクトを指すことができる型の省略形として「ポインター」を使用しました)。その意味の代替として、zeiger を提案します。 、これは単に「ポインター」を表すドイツ語です。zeiger は、別の場所を指すことができる任意の型であるため、参照のような型は zeiger ですが、ポインターのような型も同様です。

    これは、私の講演でこのベン図を使用したことを意味します:

    しかし、代わりに私はこれを提案します:

    二次プロパティ

    参照のような型とポインターのような型は、zeiger を選択するときに行う必要がある最も重要な違いです。

    ただし、同じカテゴリに分類される型の間には依然として大きな違いがあります。たとえば、const T& T& とは異なります .そして T* gsl::non_null<T*> よりも値が 1 つ多い .

    これらは二次プロパティです:

    <オール>
  • 可変性: ポインティを読めますか?指差す人に手紙を書いてもいいですか?それとも両方できますか?
  • null 可能性: ツァイガーには特別なヌル値がありますか?
  • 所有権: ツァイガーが破壊された場合、ポインティも破壊されますか?
  • これらのプロパティに基づいて、たとえば、null 許容の読み取り/書き込みポインターのような型や、null 以外の読み取り専用の参照のような型について話すことができます。したがって、上記の例では、zeiger が所有しているか非所有であるかは問題ではありません。

    実装上の理由から、null 許容の参照のような型は暗黙的なアクセス構文を持つことができないことに注意してください。したがって、上記のケース 2 (暗黙の作成と明示的なアクセス) は null 許容の参照のような型です。そして boost::optional<T&> たとえば、これらの正確なセマンティクスがあります。

    コア プロパティは「名詞」を定義し、二次プロパティは追加の「形容詞」を定義します。

    繰り返しますが、名詞は形容詞よりもはるかに重要です。null 以外のポインターのような型が必要な場合 (gsl::non_null<T*> のようなもの) または私の type_safe::object_ref<T> ) ただし、これらの型にアクセスできない場合は、T& を使用しないでください 代わりに!null ではありませんが、ポインターのような型ではありません — 参照のような型です.そして、この違いは null 可能性の違いよりも重要です.

    正しいツァイガーを選択するためのガイドライン

    ツァイガー型について話すための語彙ができたので、ツァイガー型を必要とする状況を見て、どの名詞が必要で、どの形容詞が必要かを分析できます。その後、それらのプロパティを持つ任意の型を選択できます.

    ただし、これはまさに私が講演で行ったことなので、ここですべてを繰り返すことはしません。ビデオを見るか、スライドを見ることをお勧めします。

    そこでは異なる概念名を使用したことに注意してください:

    • 「ポインターのようなタイプ」→「zeiger」
    • Alias →「参照型」
    • Reference →「ポインタ型」