39

私はすでにこの質問をどこかで見たと確信しています(comp.lang.c ++?Googleもそこで見つけられないようです)が、ここで簡単に検索しても見つからないようなので、ここにあります:

キーが存在しない場合、std::map operator[] がオブジェクトを作成するのはなぜですか? わかりませんが、他のほとんどの operator[] (std::vector など) と比較すると、これは直感に反するように思えます。使用する場合は、インデックスが存在することを確認する必要があります。std::map でこの動作を実装する理由は何だろうと思っています。私が言ったように、ベクトルのインデックスのように振る舞い、無効なキーでアクセスしたときにクラッシュする方が直感的ではないでしょうか (未定義の動作だと思います)。

答えを見た後に質問を絞り込む:

わかりましたこれまでのところ、基本的に安いので、なぜそうしないのか、または同様のことを言う多くの回答を得ました. 私はそれに完全に同意しますが、そのために専用の関数を使用しないのはなぜですか(Javaにはoperator []がなく、関数はputと呼ばれるとコメントの1つが言ったと思います)?私のポイントは、なぜ map operator[] がベクトルのように機能しないのですか? ベクトルの範囲外のインデックスで operator[] を使用すると、たとえ安価であっても要素を挿入したくありません。これは、おそらくコードのエラーを意味するためです。私のポイントは、なぜそれがマップと同じではないのかということです。つまり、私にとって、マップで operator[] を使用することは、このキーが既に存在することを知っていることを意味します (何らかの理由で、挿入しただけで、どこかに冗長性があります)。その方が直感的にわかりやすいと思います。

そうは言っても、現在の動作を operator[] で実行する利点は何ですか? 多分それはそのようにより明確なコードを与えるでしょうか?知らない。

別の答えは、それはすでにそのように存在していたので、なぜそれを保持しないのですか? したがって、私の質問は基本的に次のとおりです。なぜそのように実装することを選択するのか、つまり、他の演算子[]との一貫性がやや欠けていることを意味します。どのようなメリットがありますか?

ありがとう

4

13 に答える 13

26

値自体への参照を返すためoperator[]、問題を示す唯一の方法は例外をスローすることです(通常、STLが例外をスローすることはめったにありません)。

この動作が気に入らない場合は、map::find代わりに使用できます。値の代わりにイテレータを返します。これにより、値が見つからない場合(を返すmap::end)に特別なイテレータを返すことができますが、値を取得するにはイテレータを逆参照する必要があります。

于 2009-10-28T19:36:27.723 に答える
14

標準では、(23.3.1.2/1) operator[] が を返すと言われています(*((insert(make_pair(x, T()))).first)).second。それが理由です。参照を返しますT&。無効な参照を返す方法はありません。また、リファレンスを返すのは非常に便利ですよね。

于 2009-10-28T19:34:11.163 に答える
7

あなたの本当の質問に答えるために:なぜそれがそのように行われたのかについて説得力のある説明はありません. 「だから」。

std::map連想コンテナであるため、マップ内に存在しなければならない (または存在しない) 明確な事前定義されたキーの範囲はありません (完全に異なる状況とは対照的にstd::vector)。つまり、std::mapでは、非挿入ルックアップ機能と挿入ルックアップ機能の両方が必要です。挿入しない方法でオーバーロード[]し、挿入用の関数を提供できます。または、逆の方法で[]、挿入演算子としてオーバーロードし、非挿入検索用の関数を提供することもできます。それで、誰かが後者のアプローチに従うことに決めました。それだけです。

彼らが逆のことをしたとしたら、今日誰かがあなたの質問の逆バージョンをここで尋ねているかもしれません.

于 2009-10-28T20:12:13.520 に答える
4

これは主に、マップの場合 (たとえばベクターとは異なり)、かなり安価で簡単に作成できるためだと思います。単一の要素を作成するだけで済みます。ベクトルの場合、ベクトルを拡張して新しい添え字を有効にすることができますが、新しい添え字が既存のものをはるかに超えている場合、その時点までのすべての要素を追加すると、かなりコストがかかる可能性があります。ベクトルを拡張するときは、通常、追加する新しい要素の値も指定します (多くの場合、デフォルト値を使用します)。この場合、既存の要素と新しい要素の間のスペースにある要素の値を指定する方法はありません。

また、マップの通常の使用方法にも根本的な違いがあります。ベクターでは、通常、ベクターに追加されるものと、既にベクターにあるものと連携するものとの間に明確な線引きがあります。マップの場合、これは当てはまりません。アイテムが存在する場合はそれを操作するコード、またはアイテムがまだ存在しない場合は新しいアイテムを追加するコードがよく見られますそれぞれの operator[] の設計はそれを反映しています。

于 2009-10-28T20:30:37.897 に答える
4

それは割り当て目的のためのものです:


void test()
{
   std::map<std::string, int >myMap;
   myMap["hello"] = 5;
}
于 2009-10-28T19:34:50.080 に答える
3

次のように、を使用して新しい要素を挿入できますoperator[]

std::map<std::string, int> m;
m["five"] = 5;

は、新しく作成された要素への参照である、5によって返される値に割り当てられます。m["five"]新しい要素を挿入しない場合operator[]、これはそのようには機能しません。

于 2009-10-28T19:35:14.263 に答える
3

map.insert(キー、アイテム); キーがマップ内にあることを確認しますが、既存の値を上書きしません。

map.operator[キー] = アイテム; キーがマップ内にあることを確認し、既存の値をアイテムで上書きします。

これらの操作は両方とも、1 行のコードを保証するのに十分重要です。設計者はおそらく、operator[] にとってどちらの操作がより直感的であるかを選択し、もう一方の関数呼び出しを作成しました。

于 2009-12-18T15:26:41.980 に答える
1

ここでの違いは、マップが「インデックス」を格納することです。つまり、マップに格納される値(基礎となるRBツリー)は、std::pair単なる「インデックス」値ではなく、です。map::find()特定のキーとのペアが存在するかどうかは常にわかります。

于 2009-10-28T19:34:53.280 に答える
1

その答えは、便利で高速な実装が必要だったからです。

ベクトルの基本的な実装は配列です。したがって、配列に 10 個のエントリがあり、エントリ 5 が必要な場合、T& vector::operator[](5) 関数は headptr+5 を返すだけです。エントリ 5400 を要求すると、headptr+5400 が返されます。

マップの基礎となる実装は通常、ツリーです。標準で連続している必要があるベクトルとは異なり、各ノードは動的に割り当てられます。したがって、 nodeptr+5 は何も意味しませんし、 map["some string"] は rootptr+offset("some string") を意味しません。

マップでの find と同様に、境界チェックが必要な場合は vector に getAt() があります。ベクトルの場合、境界チェックは、それを望まない人にとっては不必要なコストと見なされていました。マップの場合、参照を返さない唯一の方法は例外をスローすることであり、それを望まない人にとっては不要なコストと見なされていました。

于 2009-10-28T20:29:23.203 に答える
0

このような入力を考えてみましょう - 3 つのブロック、各ブロックは 2 行、最初の行は 2 番目の行の要素の数です。

5
13 20 22 43 146
4
13 22 43 146
5
13 43 67 89 146

問題: 3 つのブロックすべての 2 行目にある整数の数を計算します。(このサンプル入力の場合、3 つのブロックすべての 2 行目に 13、43、および 146 が存在する限り、出力は 3 になります)

このコードの素晴らしさをご覧ください:

int main ()
{
    int n, curr;
    map<unsigned, unsigned char> myMap;
    for (int i = 0; i < 3; ++i)
    {
        cin >> n;
        for (int j = 0; j < n; ++j)
        {
            cin >> curr;
            myMap[curr]++;
        }

    }

    unsigned count = 0;
    for (auto it = myMap.begin(); it != myMap.end(); ++it)
    {
        if (it->second == 3)
            ++count;
    }

    cout << count <<endl;
    return 0;
}

の標準operator[]返品リファレンスによると(*((insert(make_pair(key, T()))).first)).second。それが私が書くことができる理由です:

myMap[curr]++;

キーを持つ要素を挿入しcurr、キーがマップに存在しない場合は値をゼロで初期化しました。また、要素がマップにあるかどうかに関係なく、値を増やしました。

簡単ですね。いいですね。これは本当に便利だという良い例です。

于 2013-05-07T18:39:27.930 に答える
0

std::map から何らかのキーを持つ要素を読み取りたいが、
それが存在するかどうかわからず、存在
しない場合に備えて、誤って挿入したくない場合は、
代わりに取得したい例外がスローされますが、要素を読み取るたび
に手動でチェックしたくない場合、map.find(key) != map.end()

使うだけmap::at(key)(C++11)

https://www.cplusplus.com/reference/map/map/at/

于 2022-01-02T03:07:30.217 に答える
0

私はこれが古い質問であることを知っていますが、誰もうまく答えていないようです。これまでのところ、これについての言及は見たことがありません。

未定義の動作の可能性は回避する必要があります! UB 以外に合理的な動作がある場合は、それを使用する必要があると思います

std::vector/arrayoperator[]これは、c/c++ で実行できる最も高速で最も基本的なことの 1 つであり、何かをチェックしようとするのは間違っているため、適切なオプションが実際には存在しないため、不適切なインデックスで未定義の動作を示します。チェックはそのためのものat()です。

std::*associative_container*インデックス付きの要素がどこに行くかを見つける作業はすでに完了しているので、そこに要素を作成して返すのは理にかなっています。これは非常に便利な動作であり、代替の方法operator[]は見栄えがよくありませんが、新しいアイテムの作成と挿入が必要でなかったり、役に立たない場合でも、未定義の動作よりもはるかに優れた結果になります。

読みやすさのために、連想コンテナーを使用するための非常に好ましい構文だと思います。operator[]私にとって、これは非常に直感的でありoperator[]、配列の概念と正確に一致ます。

「もしそこに何もなかったら」という私の直感が「未定義の動作」だけである場合、私はそれを避けるためにできる限りのことをしているので、絶対に悪いことはありません。

それからある日、私はアイテムを挿入できることを知りましたoperator[]...人生はちょうど良いです。

于 2021-07-18T05:55:37.760 に答える