web-dev-qa-db-ja.com

マップに挿入する優先/慣用的な方法

std::mapに挿入する4つの異なる方法を特定しました。

std::map<int, int> function;

function[0] = 42;
function.insert(std::map<int, int>::value_type(0, 42));
function.insert(std::pair<int, int>(0, 42));
function.insert(std::make_pair(0, 42));

どちらが優先/慣用的な方法ですか? (そして、私が考えていない別の方法がありますか?)

87
fredoverflow

まず、operator[]insertのメンバー関数は機能的に同等ではありません。

  • operator[]は、キーに対して検索を検出し、見つからない場合はデフォルトの構築値を挿入し、値を割り当てる参照を返します。明らかに、mapped_typeがデフォルトで構築および割り当てられるのではなく、直接初期化されることでメリットが得られる場合、これは非効率的です。このメソッドは、挿入が実際に行われたかどうか、または以前に挿入されたキーの値のみを上書きしたかどうかを判断することも不可能にします
  • insertメンバー関数は、キーが既にマップに存在する場合は効果がなく、忘れられがちですが、関心のあるstd::pair<iterator, bool>を返します(特に挿入が実際に行われたかどうかを判断するため)行われて)。

insertを呼び出すすべてのリストされた可能性から、3つすべてはほぼ同等です。念のため、標準のinsert署名を見てみましょう。

typedef pair<const Key, T> value_type;

  /* ... */

pair<iterator, bool> insert(const value_type& x);

では、3つの呼び出しはどのように異なるのでしょうか?

  • std::make_pairはテンプレート引数の推論に依存しており、この場合は追加の呼び出しが必要なマップの実際のvalue_typeとは異なるタイプの何かを生成する可能性があります(そしてこの場合willstd::pairに変換するためにvalue_typeテンプレートコンストラクターに(つまり:first_typeconstを追加)
  • std::pair<int, int>は、パラメーターをstd::pairに変換するためにvalue_typeのテンプレートコンストラクターへの追加の呼び出しも必要とします(つまり、first_typeconstを追加)
  • std::map<int, int>::value_typeは、insertメンバー関数で予期されるパラメータータイプであるため、疑う余地はまったくありません。

最終的に、デフォルトの構築とoperator[]の割り当てに追加コストがない限り、目的が挿入する場合はmapped_typeの使用を避け、新しいキーが効果的に挿入されました。 insertを使用する場合は、おそらくvalue_typeを作成する方法があります。

78
icecrime

C++ 11の時点では、2つの主要な追加オプションがあります。まず、insert()をリスト初期化構文で使用できます。

function.insert({0, 42});

これは機能的に同等です

function.insert(std::map<int, int>::value_type(0, 42));

しかし、はるかに簡潔で読みやすい。他の回答が指摘しているように、これには他の形式よりもいくつかの利点があります。

  • operator[]アプローチでは、マップされた型が割り当て可能である必要がありますが、常にそうであるとは限りません。
  • operator[]アプローチは、既存の要素を上書きする可能性があり、これが発生したかどうかを判断する方法を提供しません。
  • リストするinsertの他の形式は、暗黙的な型変換を伴うため、コードが遅くなる可能性があります。

主な欠点は、このフォームがキーと値がコピー可能であることを要求していたため、例えば、 unique_ptr値を持つマップ。これは標準で修正されていますが、修正はまだ標準ライブラリの実装に達していない可能性があります。

次に、emplace()メソッドを使用できます。

function.emplace(0, 42);

これはinsert()のどの形式よりも簡潔で、unique_ptrのような移動のみの型で問題なく動作し、理論的にはわずかに効率的です(ただし、適切なコンパイラーは違いを最適化する必要があります)。唯一の大きな欠点は、emplaceメソッドが通常は使用されないため、読者を少し驚かせる可能性があることです。

83
Geoff Romer

最初のバージョン:

function[0] = 42; // version 1

値42をマップに挿入する場合としない場合があります。キー0が存在する場合、42をそのキーに割り当て、そのキーが持っていた値を上書きします。それ以外の場合は、キー/値のペアを挿入します。

挿入機能:

function.insert(std::map<int, int>::value_type(0, 42));  // version 2
function.insert(std::pair<int, int>(0, 42));             // version 3
function.insert(std::make_pair(0, 42));                  // version 4

一方、キー0がマップに既に存在する場合は何もしません。キーが存在しない場合は、キー/値のペアが挿入されます。

3つの挿入関数はほぼ同じです。 std::map<int, int>::value_typestd::pair<const int, int>typedefであり、std::make_pair()は明らかにテンプレート推論マジックによりstd::pair<>を生成します。ただし、最終結果はバージョン2、3、および4で同じになります。

どちらを使用しますか?個人的にはバージョン1を好みます。簡潔で「自然」です。もちろん、上書き動作が望ましくない場合は、バージョン4をお勧めします。バージョン2および3よりも入力が少ないためです。de factoの方法が1つあるかどうかはわかりません。キーと値のペアをstd::mapに挿入します。

コンストラクターの1つを介して値をマップに挿入する別の方法:

std::map<int, int> quadratic_func;

quadratic_func[0] = 0;
quadratic_func[1] = 1;
quadratic_func[2] = 4;
quadratic_func[3] = 9;

std::map<int, int> my_func(quadratic_func.begin(), quadratic_func.end());
9
In silico

上記のバージョン間でいくつかの時間比較を実行しています。

function[0] = 42;
function.insert(std::map<int, int>::value_type(0, 42));
function.insert(std::pair<int, int>(0, 42));
function.insert(std::make_pair(0, 42));

挿入バージョン間の時間差はごくわずかです。

#include <map>
#include <vector>
#include <boost/date_time/posix_time/posix_time.hpp>
using namespace boost::posix_time;
class Widget {
public:
    Widget() {
        m_vec.resize(100);
        for(unsigned long it = 0; it < 100;it++) {
            m_vec[it] = 1.0;
        }
    }
    Widget(double el)   {
        m_vec.resize(100);
        for(unsigned long it = 0; it < 100;it++) {
            m_vec[it] = el;
        }
    }
private:
    std::vector<double> m_vec;
};


int main(int argc, char* argv[]) {



    std::map<int,Widget> map_W;
    ptime t1 = boost::posix_time::microsec_clock::local_time();    
    for(int it = 0; it < 10000;it++) {
        map_W.insert(std::pair<int,Widget>(it,Widget(2.0)));
    }
    ptime t2 = boost::posix_time::microsec_clock::local_time();
    time_duration diff = t2 - t1;
    std::cout << diff.total_milliseconds() << std::endl;

    std::map<int,Widget> map_W_2;
    ptime t1_2 = boost::posix_time::microsec_clock::local_time();    
    for(int it = 0; it < 10000;it++) {
        map_W_2.insert(std::make_pair(it,Widget(2.0)));
    }
    ptime t2_2 = boost::posix_time::microsec_clock::local_time();
    time_duration diff_2 = t2_2 - t1_2;
    std::cout << diff_2.total_milliseconds() << std::endl;

    std::map<int,Widget> map_W_3;
    ptime t1_3 = boost::posix_time::microsec_clock::local_time();    
    for(int it = 0; it < 10000;it++) {
        map_W_3[it] = Widget(2.0);
    }
    ptime t2_3 = boost::posix_time::microsec_clock::local_time();
    time_duration diff_3 = t2_3 - t1_3;
    std::cout << diff_3.total_milliseconds() << std::endl;

    std::map<int,Widget> map_W_0;
    ptime t1_0 = boost::posix_time::microsec_clock::local_time();    
    for(int it = 0; it < 10000;it++) {
        map_W_0.insert(std::map<int,Widget>::value_type(it,Widget(2.0)));
    }
    ptime t2_0 = boost::posix_time::microsec_clock::local_time();
    time_duration diff_0 = t2_0 - t1_0;
    std::cout << diff_0.total_milliseconds() << std::endl;

    system("pause");
}

これは、バージョンごとにそれぞれ与えられます(ファイルを3回実行したため、それぞれに3つの連続した時間差があります)。

map_W.insert(std::pair<int,Widget>(it,Widget(2.0)));

2198ミリ秒、2078ミリ秒、2072ミリ秒

map_W_2.insert(std::make_pair(it,Widget(2.0)));

2290ミリ秒、2037ミリ秒、2046ミリ秒

 map_W_3[it] = Widget(2.0);

2592ミリ秒、2278ミリ秒、2296ミリ秒

 map_W_0.insert(std::map<int,Widget>::value_type(it,Widget(2.0)));

2234ミリ秒、2031ミリ秒、2027ミリ秒

したがって、異なる挿入バージョン間の結果は無視できます(ただし、仮説検定は実行しませんでした)。

map_W_3[it] = Widget(2.0);バージョンは、Widgetのデフォルトコンストラクターで初期化されるため、この例では約10〜15%時間がかかります。

3
user3116431

つまり、[]演算子は、値型のデフォルトコンストラクターを呼び出して新しい値を割り当てるため、値を更新する場合により効率的です。一方、insert()演算子は、値を追加する場合により効率的です。

効果的なSTL:標準テンプレートライブラリの使用を改善するための50の具体的な方法から引用されたスニペットは、スコットマイヤーズによる、項目24に役立つかもしれません。

template<typename MapType, typename KeyArgType, typename ValueArgType>
typename MapType::iterator
insertKeyAndValue(MapType& m, const KeyArgType&k, const ValueArgType& v)
{
    typename MapType::iterator lb = m.lower_bound(k);

    if (lb != m.end() && !(m.key_comp()(k, lb->first))) {
        lb->second = v;
        return lb;
    } else {
        typedef typename MapType::value_type MVT;
        return m.insert(lb, MVT(k, v));
    }
}

汎用プログラミングのないこのバージョンを選択することもできますが、ポイントは、このパラダイム(「追加」と「更新」の区別)が非常に役立つことです。

2
galactica

挿入の別の関心を示すために、問題を少し変更します(文字列のマップ)。

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

rancking[0] = 42;  // << some compilers [gcc] show no error

rancking.insert(std::pair<int, std::string>(0, 42));// always a compile error

コンパイラが「rancking [1] = 42;」でエラーを表示しないという事実壊滅的な影響を与えることができます!

1
jo_

キー0で要素を上書きする場合

function[0] = 42;

そうでなければ:

function.insert(std::make_pair(0, 42));
1
Viktor Sehr

要素をstd :: mapに挿入する場合-insert()関数を使用し、要素を(キーで)見つけてそれに割り当てる場合-operator []を使用します。

挿入を簡単にするには、次のようにboost :: assignライブラリを使用します。

using namespace boost::assign;

// For inserting one element:

insert( function )( 0, 41 );

// For inserting several elements:

insert( function )( 0, 41 )( 0, 42 )( 0, 43 );
1