std :: mapのデフォルト値


85

キーが存在しない場合std::mapのデフォルト値のoperator[]戻り値を指定する方法はありますか?

回答:


55

いいえ、ありません。最も簡単な解決策は、これを行うための独自の無料テンプレート関数を作成することです。何かのようなもの:

#include <string>
#include <map>
using namespace std;

template <typename K, typename V>
V GetWithDef(const  std::map <K,V> & m, const K & key, const V & defval ) {
   typename std::map<K,V>::const_iterator it = m.find( key );
   if ( it == m.end() ) {
      return defval;
   }
   else {
      return it->second;
   }
}

int main() {
   map <string,int> x;
   ...
   int i = GetWithDef( x, string("foo"), 42 );
}

C ++ 11アップデート

目的:一般的な連想コンテナー、およびオプションのコンパレーターとアロケーターのパラメーターを考慮します。

template <template<class,class,class...> class C, typename K, typename V, typename... Args>
V GetWithDef(const C<K,V,Args...>& m, K const& key, const V & defval)
{
    typename C<K,V,Args...>::const_iterator it = m.find( key );
    if (it == m.end())
        return defval;
    return it->second;
}

1
素晴らしい解決策。関数テンプレートがコンパレータとアロケータのデフォルトのテンプレートパラメータを使用しないマップで機能するように、いくつかのテンプレート引数を追加することをお勧めします。
sbi 2010

3
+1、しかしとまったく同じ動作を提供するために、operator[]デフォルト値と、デフォルト値は、内部マップに挿入されるべきif ( it == m.end() )ブロック
デビッドロドリゲス- dribeas

12
@DavidOPは実際にはその動作を望んでいないと思います。構成の読み取りに同様のスキームを使用していますが、キーが欠落している場合に構成を更新したくありません。

2
@GMan boolパラメーターは、(宣言ではなく)呼び出しを見て何をするのかわからないため、スタイルが悪いと考える人もいます。この場合、「true」は「デフォルトを使用する」または「しない」を意味します。 tデフォルトを使用する」(または完全に他の何か)?列挙型は常に明確ですが、もちろんより多くのコードです。私自身、この問題について2つの考えを持っています。

2
デフォルト値がnullptrの場合、この回答は機能しませんが、stackoverflow.com / a / 26958878/297451は機能します。
ジョン

44

これは質問に正確に答えるものではありませんが、私は次のようなコードで問題を回避しました:

struct IntDefaultedToMinusOne
{
    int i = -1;
};

std::map<std::string, IntDefaultedToMinusOne > mymap;

1
これは私にとって最良の解決策です。実装が簡単で、非常に柔軟性があり、汎用的です。
acegs

11

C ++標準(23.3.1.2)は、新しく挿入された値がデフォルトで構築されることを指定しているため、それmap自体はそれを行う方法を提供していません。あなたの選択は次のとおりです。

  • 値型に、必要な値に初期化するデフォルトのコンストラクターを指定するか、
  • デフォルト値を提供し、operator[]そのデフォルトを挿入するように実装する独自のクラスでマップをラップします。

8
正確には、新しく挿入された値は初期化された値(8.5.5)なので、次のようになります。-Tがユーザー宣言コンストラクター(12.1)を持つクラス型の場合、Tのデフォルトコンストラクターが呼び出されます(初期化は正しくありません) -Tにアクセス可能なデフォルトコンストラクタがない場合に形成されます); — Tがユーザー宣言コンストラクターのない非ユニオンクラスタイプである場合、Tのすべての非静的データメンバーと基本クラスコンポーネントは値で初期化されます。— Tが配列型の場合、各要素は値で初期化されます。—それ以外の場合、オブジェクトはゼロで初期化されます
Tadeusz Kopec 2010

6

より一般的なバージョン、C ++ 98/03およびその他のコンテナをサポート

一般的な連想コンテナで動作します。唯一のテンプレートパラメータはコンテナタイプ自体です。

サポートされているコンテナ:std::mapstd::multimapstd::unordered_mapstd::unordered_multimapwxHashMapQMapQMultiMapQHashQMultiHash、など

template<typename MAP>
const typename MAP::mapped_type& get_with_default(const MAP& m, 
                                             const typename MAP::key_type& key, 
                                             const typename MAP::mapped_type& defval)
{
    typename MAP::const_iterator it = m.find(key);
    if (it == m.end())
        return defval;

    return it->second;
}

使用法:

std::map<int, std::string> t;
t[1] = "one";
string s = get_with_default(t, 2, "unknown");

ラッパークラスを使用した同様の実装を次に示します。これは、Pythonget()dict型のメソッドにより類似しています。https//github.com/hltj/wxMEdit/blob/master/src/xm/xm_utils.hpp

template<typename MAP>
struct map_wrapper
{
    typedef typename MAP::key_type K;
    typedef typename MAP::mapped_type V;
    typedef typename MAP::const_iterator CIT;

    map_wrapper(const MAP& m) :m_map(m) {}

    const V& get(const K& key, const V& default_val) const
    {
        CIT it = m_map.find(key);
        if (it == m_map.end())
            return default_val;

        return it->second;
    }
private:
    const MAP& m_map;
};

template<typename MAP>
map_wrapper<MAP> wrap_map(const MAP& m)
{
    return map_wrapper<MAP>(m);
}

使用法:

std::map<int, std::string> t;
t[1] = "one";
string s = wrap_map(t).get(2, "unknown");

typename MAP :: mapped_type&defvalがスコープ外になる可能性があるため、MAP :: mapped_type&を返すのは安全ではありません。
ジョー


5

デフォルト値を指定する方法はありません。常にデフォルトで構築された値です(ゼロパラメーターコンストラクター)。

実際operator[]、マップ内の特定のキーに値が存在しないかのように、おそらく予想以上のことを行い、デフォルトのコンストラクターからの値を使用して新しいキーを挿入します。


2
そうです、新しいエントリの追加を避けるfindために、特定のキーの要素が存在しない場合に終了イテレータを返す使用できます。
Thomas Schaub 2010

@ThomasSchaubfindその場合の時間計算量はどれくらいですか?
ajaysinghnegi

5
template<typename T, T X>
struct Default {
    Default () : val(T(X)) {}
    Default (T const & val) : val(val) {}
    operator T & () { return val; }
    operator T const & () const { return val; }
    T val;
};

<...>

std::map<KeyType, Default<ValueType, DefaultValue> > mapping;

3
次に、それが機能するように変更します。このコードが実行するように設計されていないケースをわざわざ修正するつもりはありません。
Thomas Eding 2016

3

他の回答が言うように、値はデフォルトのコンストラクターを使用して初期化されます。ただし、単純な型(int、float、pointer、POD(古いデータの計画)型などの整数型)の場合、値はゼロで初期化されます(または値の初期化によってゼロになります(これは事実上です)。同じこと)、使用されているC ++のバージョンによって異なります)。

とにかく、肝心なのは、単純なタイプのマップは新しいアイテムを自動的にゼロ初期化するということです。そのため、場合によっては、デフォルトの初期値を明示的に指定することを心配する必要はありません。

std::map<int, char*> map;
typedef char *P;
char *p = map[123],
    *p1 = P(); // map uses the same construct inside, causes zero-initialization
assert(!p && !p1); // both will be 0

タイプ名の後の括弧はnewと違いがありますか?を参照してください。問題の詳細については。


2

回避策の1つは、のmap::at()代わりにを使用することです[]。キーが存在しない場合、at例外をスローします。さらに良いことに、これはベクトルでも機能するため、マップをベクトルと交換できるジェネリックプログラミングに適しています。

未登録のキーにカスタム値を使用すると、そのカスタム値(-1など)がコードのさらに下で処理される可能性があるため、危険な場合があります。例外を除いて、バグを見つけるのは簡単です。


1

たぶん、あなたはあなたが望むデフォルト値で割り当てるカスタムアロケーターを与えることができます。

template < class Key, class T, class Compare = less<Key>,
       class Allocator = allocator<pair<const Key,T> > > class map;

4
operator[]T()アロケータが何を実行しても、を呼び出して作成されたオブジェクトを返します。
sbi 2010

1
@sbi:マップはallocatorsconstructメソッドを呼び出しませんか?それを変えることは可能だと思います。とはいえ、整形式constructnew(p) T(t);はない以外のことをしない関数だと思います。編集:後から考えると、それは愚かでした。そうでなければ、すべての値は同じになります:P私のコーヒーはどこにありますか...
GManNickG 2010

1
@GMan:C ++ 03の私のコピーは(23.3.1.2で)それをoperator[]返します(*((insert(make_pair(x, T()))).first)).second。だから私が何かを逃していない限り、この答えは間違っています。
sbi 2010

あなたが正しいです。しかし、それは私には間違っているようです。なぜ彼らは挿入にアロケーター機能を使わないのですか?
VDVLeon 2010

2
@sbi:いいえ、私はこの答えが間違っていることに同意しますが、別の理由があります。コンパイラは確かにありませんinsertT()、それは新しいのためのアロケータのgetメモリを使用する際に内部の挿入がありT、その後の呼び出しconstructで、それが与えられたパラメータとそのメモリ上T()。したがって、動作を変更operator[]して別のものを返すようにすることは確かに可能ですが、アロケーターはそれが呼び出されている理由を区別できません。したがってconstruct、そのパラメーターを無視して特別な値を使用したとしても、構築されたすべての要素がその値を持っていることを意味します。これは悪いことです。
GManNickG 2010

1

答えhttps://stackoverflow.com/a/2333816/272642を拡張すると、このテンプレート関数はstd::map'sを使用しますkey_typemapped_typeのtypedefはの種類を推定するkeydef。これは、これらのtypedefがないコンテナでは機能しません。

template <typename C>
typename C::mapped_type getWithDefault(const C& m, const typename C::key_type& key, const typename C::mapped_type& def) {
    typename C::const_iterator it = m.find(key);
    if (it == m.end())
        return def;
    return it->second;
}

これにより、

std::map<std::string, int*> m;
int* v = getWithDefault(m, "a", NULL);

のような引数をキャストする必要はありませんstd::string("a"), (int*) NULL


1

使用する std::map::insert()ます。

私はこのパーティーにかなり遅れていることに気づきましたがoperator[]、カスタムデフォルトの動作に興味がある場合(つまり、指定されたキーを持つ要素を見つけ、それが存在しない場合は、マップに要素を挿入します選択されたデフォルト値と、新しく挿入された値または既存の値のいずれかへの参照を返します)、C ++ 17より前に使用可能な関数がすでにありますstd::map::insert()insertキーがすでに存在する場合は実際には挿入されませんが、代わりに既存の値にイテレータを返します。

たとえば、文字列から整数へのマップが必要で、キーがまだ存在しない場合はデフォルト値の42を挿入するとします。

std::map<std::string, int> answers;

int count_answers( const std::string &question)
{
    auto  &value = answers.insert( {question, 42}).first->second;
    return value++;
}

int main() {

    std::cout << count_answers( "Life, the universe and everything") << '\n';
    std::cout << count_answers( "Life, the universe and everything") << '\n';
    std::cout << count_answers( "Life, the universe and everything") << '\n';
    return 0;
}

42、43、44を出力するはずです。

マップ値を作成するコストが高い場合(キーのコピー/移動または値型のいずれかが高価な場合)、これには重大なパフォーマンスの低下が伴います。これは、C ++ 17で回避できると思いますtry_emplace

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.