http://www.cplusplus.com/reference/utility/pair/ から、_std::pair
_にはfirst
とsecond
の2つのメンバー変数があることがわかります。
STLデザイナーが、getFirst()
とgetSecond()
を提供する代わりに、2つのメンバー変数first
とsecond
を公開することにしたのはなぜですか?
元のC++ 03 _std::pair
_の場合、メンバーにアクセスする関数は有用な目的を果たしません。
C++ 11以降(現在C++ 14で、C++ 17が高速になっています)_std::pair
_は_std::Tuple
_の特殊なケースです。ここで_std::Tuple
_アイテムをいくつでも持つことができます。そのため、パラメータ化されたゲッターを持つことは理にかなっています。なぜなら、任意の数のアイテム名を発明して標準化することは非現実的だからです。したがって、 _std::get
_ を_std::pair
_にも使用できます。
したがって、設計の理由は歴史的であり、現在の_std::pair
_は、より一般的なものへの進化の最終結果です。
ほかのニュースでは:
に関して
」私の知る限り、上記の2つのメンバー変数をカプセル化し、
getFirst();
とgetSecond()
いいえ、それはごみです。
それは、釘を打つ、ねじで固定する、木片を切り取るなど、ハンマーが常に優れていると言うようなものです。特に最後のケースでは、ハンマーは有用なツールではありません。ハンマーは非常に便利ですが、一般的に「より良い」という意味ではありません。それは単なるナンセンスです。
値の取得または設定には追加のロジック(内部状態の変更)が必要だと考える場合、ゲッターとセッターは通常便利です。これは、メソッドに簡単に追加できます。この場合 std::pair
は、2つのデータ値を提供するためにのみ使用されます。これ以上でもそれ以下でもありません。したがって、ゲッターとセッターの冗長性を追加しても意味がありません。
理由は、std::pair
が2つの要素の汎用コンテナをモデル化するため、データ構造に実際の不変条件を課す必要がないためです。言い換えると、タイプstd::pair<T, U>
のオブジェクトは、タイプfirst
およびsecond
のT
およびU
要素に対して有効であると想定されます。 、それぞれ。同様に、その要素の値のその後の変異は、std::pair
自体の有効性に実際に影響を与えることはできません。
アレックスステパノフ(STLの著者) 明示的に提示 コース中のこの一般的な設計原則コメント付きのコンポーネントによる効率的なプログラミングsingleton
コンテナ(つまり、1つの要素のコンテナ)。
したがって、原則自体が議論の源になる可能性はありますが、これがstd::pair
の形の背後にある理由です。
ゲッターとセッターは、抽象化が設計の選択とそれらの選択の変更からユーザーを隔離するために、現在または将来的に保証されると信じる場合に役立ちます。
「今」の典型的な例は、セッター/ゲッターに値を検証および/または計算するロジックがあることです。たとえば、フィールドを直接公開するのではなく、電話番号にセッターを使用して、フォーマットを確認できます。ゲッターがメンバーの値(コレクション)の読み取り専用ビューを呼び出し元に提供できるように、コレクションにゲッターを使用します。
「将来の変更」の標準( though bad )の例はPoint
です-x
とy
またはgetX()
およびgetY()
?通常の答えは、ゲッター/セッターを使用することです。将来的には、内部表現をデカルトからポーラーに変更したい場合があるため、 tユーザーに影響を与えたい(または、その設計決定に依存させる)。
の場合 std::pair
-このクラスは、現在も永久に(任意の型の)2つの値と正確に2つの値を直接表し、それらの値を要求に応じて提供することを目的としています。それでおしまい。そして、それがデザインがゲッター/セッターを通過するのではなく、直接メンバーアクセスを使用する理由です。
std::pair
は、そのメンバーにアクセスするためのアクセサー関数を持つ方が良いでしょう!特に、std::pair
利点がある可能性があります。たとえば、タイプの少なくとも1つが空の非最終クラスである場合、オブジェクトを小さくすることができます(空のベースは、独自のアドレスを取得する必要のないベースにすることができます)。
当時の std::pair
は、これらの特殊なケースが考慮されないように考案されました(そして、その時点でドラフト作業ペーパーで空のベース最適化が許可されたかどうかはわかりません)。しかし、セマンティックの観点からは、アクセサー関数を使用する理由はあまりありません。明らかに、アクセサーは非const
オブジェクトの可変参照を返す必要があります。その結果、アクセサーはカプセル化の形式を提供しません。
一方で、アクセプター関数が使用されているときに何が起こっているかをオプティマイザーで確認することは[少し]難しくなります。追加のシーケンスポイントが導入されるためです。メン・リーとアレクサンダー・ステパノフは、違いがあるかどうかさえ測定したと想像できました(私もしませんでした)。たとえそうでなかったとしても、メンバーへの直接アクセスを提供することは、アクセサー関数を経由するよりも確かに遅くありませんが、逆は必ずしも真実ではありません。
私は決定の一部ではなかったし、C++標準には根拠がありませんが、私はguessメンバーをパブリックデータメンバーにするという意図的な決定でした。
ゲッターとセッターの主な目的は、アクセスを制御することです。つまり、 "first"を変数として公開すると、どのクラスでも、そのクラスの一部であることを通知せずに(const
でない場合)読み書きできます。多くの場合、それは深刻な問題を引き起こす可能性があります。
たとえば、ボートに乗っている乗客の数を表すクラスがあるとします。乗客の数を整数として保存します。その番号を裸の変数として公開すると、外部関数がその番号に書き込むことが可能になります。実際には10人の乗客がいるが、誰かが変数を(おそらく偶然に)50に変更した場合、これで問題が発生する可能性があります。これは、乗客数のゲッターの場合です(ただし、同じ問題)。
ゲッターとセッターの例は、ベクトルに関する特定の情報をキャッシュする数学的なベクトルを表すクラスです。長さを保存するとします。この場合、vec.x
はおそらく長さ/大きさを変えるでしょう。したがって、xをゲッターでラップする必要があるだけでなく、xのセッターを提供する必要があります。これは、ベクターのキャッシュされた長さを更新することを知っています。 (もちろん、ほとんどの実際の数学ライブラリはこれらの値をキャッシュしないため、変数を公開します。)
したがって、それらを使用するコンテキストで自分自身に問うべき質問は次のとおりです:このクラスeverは、おそらくこの変数?
Std :: pairのような答えは、フラットな「いいえ」です。それらのメンバーを含めることが唯一の目的であるクラスのメンバーへのアクセスを制御する場合はありません。これらの変数が2つだけのメンバーであるため、ペアがこれらの変数に触れたかどうかを知る必要はありません。したがって、変更しても更新する状態はありません。ペアは、実際に含まれているものとその意味を知らないため、含まれているものを追跡することは努力する価値がありません。
コンパイラとその構成方法によっては、ゲッターとセッターがオーバーヘッドを引き起こす可能性があります。ほとんどの場合、それはおそらく重要ではありませんが、std::pair
、それは些細なことではありません。そのため、それらの追加には正当化が必要になります。これは、先ほど説明したように、そうすることはできません。
私は、オブジェクト指向設計の基本的な理解を示さないコメントの数にapp然としました(c ++がオブジェクト指向言語ではないことを証明していますか?)。はい、std :: pairの設計にはいくつかの歴史的な特徴がありますが、それは悪い設計を良いものにしません。また、事実を否定する言い訳として使用されるべきではありません。それを暴言する前に、コメントの質問のいくつかに答えさせてください:
Intにもセッターとゲッターが必要だとは思わないか
はい、設計の観点からアクセサを使用する必要があります。アクセサを使用すると、損失がなくなるだけで、柔軟性が向上します。一部の新しいアルゴリズムでは、追加のビットをキー/値にパックしたい場合があり、アクセサーなしではエンコード/デコードできません。
ゲッターにロジックがない場合、なぜゲッターで何かをラップするのですか?
ゲッター/セッターにロジックがないことをどのように知っていますか?優れた設計は、推測に基づいた実装の可能性を制限すべきではありません。可能な限りの柔軟性を提供する必要があります。 std:pairの設計もイテレータの設計を決定することを忘れないでください。また、ユーザーがメンバー変数に直接アクセスすることを要求することにより、イテレータは実際にキー/値を一緒に格納する構造を返す必要があります。それは大きな制限であることが判明しました。それらを分離する必要があるアルゴリズムがあります。キー/値をまったく明示的に保存しないアルゴリズムがあります。次に、反復中にデータをコピーする必要があります。
一般的な信念に反して、ゲッターとセッターでメンバー変数を保存するだけのオブジェクトを持つことは、「物事が行われるべき方法」ではありません
別のワイルドな推測。
はい、ここでやめます。
元の質問に答えるために:std :: pairはメンバー変数を公開することを選択しました。なぜなら、それを設計した人は誰でも柔軟な契約の重要性を認識および/または優先しなかったからです。彼らは明らかに、マップ/ハッシュテーブルのキーと値のペアをどのように実装すべきかについて非常に狭いアイデア/ビジョンを持っていました。さらに悪いことに、彼らは実装に関するそのような狭い視野をデザインに妥協するためにトップに溢れさせました。たとえば、線形プローブを使用したオープンアドレッシングスキームに基づいて、キーと値を別々の配列に保存するstd:unordered_mapの置換を実装する場合はどうなりますか?これにより、キーをプローブするために値が占めるスペースを横切ってジャンプする必要がないため、小さなキーと大きな値を持つペアのキャッシュパフォーマンスが大幅に向上します。 std :: pairがアクセサを選択した場合、このためにSTLスタイルのイテレータを作成するのは簡単です。しかし、今では、追加のデータコピーを誘発せずにこれを達成することはまったく不可能です。
また、std :: unordered_mapの実装には、オープンハッシュ(つまり、クローズドチェーン)の使用が義務付けられていることに気付きました。これは設計の観点から奇妙なだけでなく(実装方法を制限したいのはなぜですか?)、実装の面でもかなり愚かです-リンクリストを使用した連鎖ハッシュテーブルは、おそらくすべてのカテゴリの中で最も遅いです。ウェブをグーグルで検索すると、std:unordered_mapがハッシュテーブルベンチマークの玄関口であることがよくあります。 JavaのHashMapよりも遅くなる傾向さえあります(HashMapはチェーン化されたハッシュテーブルでもあるため、この場合にどのように遅れを取ったのかわかりません)。古い言い訳として、load_factorが1に近づくとチェーンハッシュテーブルのパフォーマンスが向上する傾向があります。これは、1)この問題に対処するためのオープンアドレスファミリに多くの技術があるためです。後者は実際に30年もの間奇妙な存在でした。 2)チェーン化されたハッシュテーブルは、エントリごとにポインタのオーバーヘッド(64ビットマシンでは8バイト)を追加するため、unordered_mapのload_factorが1に近づくと、メモリ使用率は100%ではありません!それを考慮に入れて、unordered_mapのパフォーマンスを同じメモリ使用量の代替と比較する必要があります。そして、Google Dense HashMapのような代替手段はstd :: unordered_mapよりも3〜4倍高速であることがわかりました。
これらが関連する理由は?興味深いことに、オープンハッシュを強制すると、std :: pairの設計の見栄えが悪くなります。これは、代替のストレージ構造の柔軟性が不要になったためです。さらに、std :: pairの存在により、std :: unordered_mapのドロップイン置換を記述するために、より新しい/より良いアルゴリズムを採用することはほとんど不可能になります。 std :: pairの貧弱な設計とstd :: unordered_mapの歩行者実装が一緒に長く生き残ることができるように、意図的にそれを行ったかどうか疑問に思うことがあります。もちろん私は冗談を言っているので、それらを書いた人はだれでも気を悪くしないでください。実際、JavaまたはPython(OK、Pythonのストレッチを認める))を使用している人は、「できるだけ早くC++」。