12

私は現在 C++ を学んでおり、それに付随する標準的なデータ構造に慣れようとしていますが、それらはすべて非常にむき出しに見えます。たとえば、list には、Java で慣れ親しんだ get(index) のような単純なアクセサーがありません。pop_back や pop_front などのメソッドも、リスト内のオブジェクトを返しません。したがって、次のようなことをする必要があります。

Object blah = myList.back();
myList.pop_back();

次のような単純なものの代わりに: Object blah = myList.pop_back();

Java では、ほぼすべてのデータ構造がオブジェクトを返すため、これらの余分な呼び出しを行う必要はありません。C++ の STL コンテナーがこのように設計されているのはなぜですか? 私が Java で行うこのような一般的な操作は、C++ ではあまり一般的ではありませんか?

編集:申し訳ありませんが、私の質問はこれらすべての反対票を得るには非常に不十分な言葉遣いであったと思いますが、誰かがそれを編集した可能性があります. 明確にするために、Javaと比較して、なぜSTLデータ構造がこのように作成されるのか疑問に思っています。それとも、そもそも間違ったデータ構造のセットを使用していますか? 私の要点は、これらは (私の例では) リストに対して使用する一般的な操作のように見え、誰もが毎回独自の実装を作成したくないということです。

編集:より明確になるように質問を言い換えました。

4

7 に答える 7

11

あなたが提起した特定の点についてかなりの数の人がすでに回答しているので、全体像を少し見てみましょう.

Java と C++ の根本的な違いの 1 つは、C++ が主に値を処理するのに対し、Java は主に参照を処理することです。

たとえば、次のようなものがあるとします。

class X {
    // ...
};

// ...
X x;

Java では、 X 型のオブジェクトへの参照xのみです。参照する型の実際のオブジェクトを取得するには、通常、次のようなものを使用します。ただし、C++ では、 はそれ自体で、オブジェクトへの参照だけでなく、タイプ のオブジェクトを定義します。そのオブジェクトは、参照 (つまり、変装したポインター) を介してではなく、直接使用できます。XX x = new X;X x;X

これは最初はかなり些細な違いのように思えるかもしれませんが、その影響はかなり大きく、広範囲に及びます。1 つの効果 (この場合はおそらく最も重要) は、Java では、値を返す際にオブジェクト自体をコピーする必要がまったくないことです。値への参照をコピーするだけです。これは通常、非常に安価であり、(おそらくもっと重要なことに) 完全に安全であると想定されています。例外をスローすることはありません。

C++ では、代わりに値を直接扱っています。オブジェクトを返すときは、既存のオブジェクトへの参照を返すだけでなく、通常はそのオブジェクトの状態のコピーの形式で、そのオブジェクトの値を返します。もちろん、必要に応じて参照 (またはポインター) を返すこともできますが、それを実現するには、明示的にする必要があります。

標準のコンテナーは、(どちらかといえば) 参照よりも値を操作することを重視しています。コレクションに値を追加すると、渡された値のコピーが追加され、何かを取得すると、コンテナー自体にあった値のコピーが取得されます。

とりわけ、これは、値を返すことは Java のように安価で安全である可能性がある一方で、高価であったり、例外をスローしたりする可能性があることを意味します。プログラマーがポインターを格納したい場合は、確かにそうすることができますが、言語はJava のようにそれを必要としません。オブジェクト返すことは高価であったり、スローされたりする可能性があるため、標準ライブラリのコンテナは一般に、コピーにコストがかかる場合でも適切に機能し、(さらに重要なことに) コピーによって例外がスローされた場合でも正しく機能するように構築されています。

このデザインの基本的な違いは、あなたが指摘した違いだけでなく、さらに多くの違いを説明しています.

于 2012-10-29T21:15:38.247 に答える
5

back()ベクトルの最後の要素への参照を返すため、ほぼ自由に呼び出すことができます。 pop_back()ベクトルの最後の要素のデストラクタを呼び出します。

したがってpop_back()、破棄された要素への参照を返すことは明らかにできません。したがって、構文が機能するにpop_back()は、要素が破棄される前に要素のコピーを返す必要があります。

そのコピーが不要な場合は、不必要にコピーを作成しました。

C++ 標準コンテナーの目標は、使いやすい素敵なドレッシングに包まれたベアメタルに近いパフォーマンスを提供することです。ほとんどの場合、使いやすさのためにパフォーマンスを犠牲にすることはありません。またpop_back()、最後の要素のコピーを返す は、使いやすさのためにパフォーマンスを犠牲にすることになります。

pop-and-get-back メソッドが存在する可能性がありますが、他の機能と重複します。また、多くの場合、バック アンド ポップよりも効率的ではありません。

具体例として、

vector<foo> vec; // with some data in it
foo f = std::move( vec.back() ); // tells the compiler that the copy in vec is going away
vec.pop_back(); // removes the last element

余分な一時コピーの作成を避けるために、要素が破棄される前に移動を行う必要があることに注意してください... pop_back_and_get_value()返される前に要素を破棄する必要があり、返された後に割り当てが行われるため、無駄です。

于 2012-10-29T20:05:50.230 に答える
4

インデックスによるリンク リストへのアクセスは非常に非効率的であるため、リストには get(index) メソッドがありません。STL には、ある程度効率的に実装できるメソッドのみを提供するという哲学があります。効率が悪くてもインデックスでリストにアクセスしたい場合は、自分で実装するのは簡単です。

pop_back がコピーを返さない理由は、関数が戻った後に戻り値のコピー コンストラクターが呼び出されるためです (RVO/NRVO を除く)。このコピー コンストラクターが例外をスローする場合、適切にコピーを返さずにリストから項目を削除しています。これは、メソッドが例外セーフではないことを意味します。2 つの操作を分離することで、STL は例外セーフな方法でのプログラミングを促進します。

于 2012-10-29T20:11:22.650 に答える
3

C++ の STL コンテナーがこのように設計されているのはなぜですか?

Bjarne Stroustrupはそれを最もよく表現していると思います:

C++ は無駄がなく意地悪です。基本原則は、使用しないものには料金を支払わないということです。

アイテムを返すメソッドの場合、pop()アイテムを削除して返すために、そのアイテムを参照によって返すことができないことを考慮してください。pop()指示対象は編集されたばかりなので、もう存在しません。ポインターで返すこともできますが、元のコピーを作成する場合に限りますnew。これは無駄です。そのため、深いコピーを作成する可能性がある値によって返される可能性が最も高くなります。多くの場合、(コピー省略による) ディープ コピーは作成されません。ただし、大規模なバッファーなど、場合によっては、そのコピーが非常に高価になる可能性があり、リソース ロックなどの一部のケースでは、コピーが不可能になることさえあります。

C++ は汎用であることを意図しており、可能な限り高速であることを意図しています。汎用とは、必ずしも「単純なユースケースで使いやすい」という意味ではなく、「幅広いアプリケーションに適したプラットフォーム」という意味です。

于 2012-10-29T20:58:02.827 に答える
2

のような関数に関してpop()は、(少なくとも) 考慮すべきことが 2 つあります。

pop_back()1) 返品する場合、またはpop_front()返品するものが何もない場合の明確で安全なアクションがありません。

2) これらの関数は値によって返されます。コンテナーに格納されている型のコピー コンストラクターで例外がスローされた場合、アイテムはコンテナーから削除され、失われます。これは望ましくなく、安全でないと見なされたと思います。

リストへのアクセスに関しては、非効率な操作を避けることは標準ライブラリの一般的な設計原則です。std::listは二重にリンクされたリストであり、インデックスによってリスト要素にアクセスすることは、目的の位置に到達するまでリストを最初または最後からトラバースすることを意味します。これを行いたい場合は、独自のヘルパー関数を提供できます。しかし、要素へのランダム アクセスが必要な場合は、おそらくリスト以外の構造を使用する必要があります。

于 2012-10-29T19:58:55.070 に答える
2

list には get(index) のような単純なアクセサさえありません

なぜそれが必要なのですか?から n 番目の要素にアクセスできるメソッドは、操作listの複雑さを隠しO(n)ます。これが、C++ が提供しない理由です。同じ理由で、C++は関数をstd::vector提供しません。その関数もベクトルのサイズになるからです。pop_front()O(N)

pop_back や pop_front などのメソッドも、リスト内のオブジェクトを返しません。

その理由は、例外の安全性です。std::listまた、C++ には自由な関数があるため、標準コンテナーの操作に対してそのような拡張を作成することは難しくありません。

template<class Cont>
typename Cont::value_type return_pop_back(Cont& c){
  typename Cont::value_type v = c.back();
  c.pop_back();
  return v;
}

ただし、上記の関数は例外セーフではないことに注意してください。つまり、return v;スローされた場合、コンテナーが変更され、オブジェクトが失われます。

于 2012-10-29T20:06:24.263 に答える
1

Java ではpop、一般的なインターフェイスの a は、ポップされたオブジェクトへの参照を返すことができます。

C++ では、対応するものを返すことは、値で返すことです。

ただし、移動不可能な非 POD オブジェクトの場合、コピーの構築で例外がスローされる可能性があります。次に、要素は削除されますが、クライアント コードからアクセスできるようにはなっていません。便利な値渡しポッパーは、より基本的なインスペクターと純粋なポッパーの観点から常に定義できますが、その逆はできません。

これも哲学の違いです。

C++ では、標準ライブラリは基本的なビルディング ブロックのみを提供し、直接使用できる機能は提供しません (一般的に)。何千ものサードパーティのライブラリから自由に選択できるという考えですが、その選択の自由には、使いやすさ、移植性、トレーニングなどの面で大きな代償が伴います。対照的に、Java では必要なものはほとんどすべて揃っています (典型的な Java プログラミング) は標準ライブラリに含まれていますが、事実上自由に選択することはできません (これは別の種類のコストです)。

于 2012-10-29T21:02:32.837 に答える