web-dev-qa-db-ja.com

キーが存在しないのに、std :: map operator []がオブジェクトを作成するのはなぜですか?

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

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

答えを見た後、私の質問を洗練する:

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

つまり、operator []を使用して現在の動作を行うことの利点は何ですか(そのためだけに、operator []ではなく、現在の動作を使用する関数が存在する必要があることに同意します)。多分それはそのようにより明確なコードを与えるのでしょうか?知りません。

別の答えは、それはすでにそのように存在していたので、なぜそれを維持しないのですか?しかし、おそらく彼ら(stlの前のもの)がそれをそのように実装することを選択したとき、彼らはそれが利点か何かを提供することを発見しましたか?だから私の質問は基本的に:なぜそれをそのように実装することを選ぶのか、それは他の演算子[]との一貫性のいくらかの欠如を意味します。それはどのような利益をもたらしますか?

ありがとう

34
n1ckp

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

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

22

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

13

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

std::mapassociativeコンテナであるため、(std::vector)。つまり、std::mapでは、非挿入と挿入の両方のルックアップ機能が必要です。 []を挿入しない方法でオーバーロードし、挿入するための関数を提供することができます。または、逆の方法で行うこともできます。挿入演算子として[]をオーバーロードし、非挿入検索用の関数を提供します。それで、誰かがいつか後者のアプローチに従うことに決めました。これですべてです。

もし彼らがそれを逆にしたとしたら、多分今日誰かがあなたの質問の逆のバージョンをここで尋ねているでしょう。

7
AnT

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


void test()
{
   std::map<std::string, int >myMap;
   myMap["hello"] = 5;
}
4
EToreo

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

マップの通常の使用方法にも根本的な違いがあります。ベクトルを使用すると、通常、ベクトルに追加されるものと、ベクトルにすでに存在するもので機能するものとの間に明確な線引きがあります。マップの場合、それはそれほど真実ではありません-それははるかにある場合はそこにあるアイテムを操作するコード、またはまだない場合は新しいアイテムを追加するコードを見るのがより一般的です。それぞれのoperator []のデザインはそれを反映しています。

4
Jerry Coffin

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

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

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

3
sth

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

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

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

3
me.

ここでの違いは、マップが「インデックス」を格納することです。つまり、マップ(その基になるRBツリー)に格納される値は、「インデックス付き」値だけでなく、_std::pair_です。特定のキーとのペアが存在するかどうかを通知するmap::find()が常にあります。

1

答えは、便利で高速な実装を望んでいたためです。

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

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

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

1
jmucchiello

そのような入力を考えてみましょう-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の要素を挿入し、キーがマップに存在しない場合は値をゼロで初期化しました。また、要素がマップにあるかどうかに関係なく、値をインクリメントしました。

どれだけ簡単か分かりますか?いいですね。これは本当に便利な良い例です。

0
Narek