148

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

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));

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

4

9 に答える 9

126

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唯一の大きな欠点は、メソッドは通常そのように使用されないため、読者を少し驚かせる可能性があることです。

于 2013-11-13T00:21:00.280 に答える
102

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

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

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

typedef pair<const Key, T> value_type;

  /* ... */

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

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

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

operator[]最終的に、デフォルトの構築と割り当てに追加コストがなくmapped_type、新しいキーが効果的に挿入されたかどうかを判断する必要がない場合を除き、挿入が目的の場合は使用を避けます。を使用する場合は、おそらく a をinsert作成するのvalue_typeがよいでしょう。

于 2010-11-26T16:19:27.697 に答える
13

最初のバージョン:

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_typetypedefforstd::pair<const int, int>であり、std::make_pair()明らかにstd::pair<>via テンプレート演繹魔法を生成します。ただし、最終結果は、バージョン 2、3、および 4 で同じになるはずです。

どちらを使用しますか?個人的にはバージョン 1 の方が好みです。簡潔で「自然」です。もちろん、その上書き動作が望ましくない場合は、バージョン 2 および 3 よりもタイピングが少なくて済むため、バージョン 4 を使用することを勧めします。 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());
于 2010-11-26T15:53:56.283 に答える
10

C++17 std::mapは と の 2 つの新しい挿入メソッドを提供するinsert_or_assign()ため、sp2dannytry_emplace()のコメントにも記載されています。

insert_or_assign()

基本的に、insert_or_assign()の「改良」版ですoperator[]。とは対照的にoperator[]insert_or_assign()マップの値の型がデフォルトで構築可能である必要はありません。たとえば、次のコードはMyClass、既定のコンストラクターがないため、コンパイルされません。

class MyClass {
public:
    MyClass(int i) : m_i(i) {};
    int m_i;
};

int main() {
    std::map<int, MyClass> myMap;

    // VS2017: "C2512: 'MyClass::MyClass' : no appropriate default constructor available"
    // Coliru: "error: no matching function for call to 'MyClass::MyClass()"
    myMap[0] = MyClass(1);

    return 0;
}

ただし、次の行で置き換えるmyMap[0] = MyClass(1);と、コードがコンパイルされ、挿入が意図したとおりに行われます。

myMap.insert_or_assign(0, MyClass(1));

さらに、 と同様にinsert()、 をinsert_or_assign()返しますpair<iterator, bool>。ブール値はtrue、挿入が発生したfalseかどうか、および割り当てが行われたかどうかです。イテレータは、挿入または更新された要素を指します。

try_emplace()

上記と同様に、try_emplace()の「改善」ですemplace()。とは対照的にemplace()try_emplace()キーがマップに既に存在するために挿入が失敗した場合、引数は変更されません。たとえば、次のコードは、マップに既に格納されているキーを持つ要素を配置しようとします (* を参照)。

int main() {
    std::map<int, std::unique_ptr<MyClass>> myMap2;
    myMap2.emplace(0, std::make_unique<MyClass>(1));

    auto pMyObj = std::make_unique<MyClass>(2);    
    auto [it, b] = myMap2.emplace(0, std::move(pMyObj));  // *

    if (!b)
        std::cout << "pMyObj was not inserted" << std::endl;

    if (pMyObj == nullptr)
        std::cout << "pMyObj was modified anyway" << std::endl;
    else
        std::cout << "pMyObj.m_i = " << pMyObj->m_i <<  std::endl;

    return 0;
}

出力 (少なくとも VS2017 と Coliru の場合):

pMyObj は挿入されませんでし
た pMyObj は変更されました

ご覧のとおり、pMyObjもはや元のオブジェクトを指していません。auto [it, b] = myMap2.emplace(0, std::move(pMyObj));ただし、次のコードで置き換えると、pMyObj変更されないため、出力が異なって見えます。

auto [it, b] = myMap2.try_emplace(0, std::move(pMyObj));

出力:

pMyObj は挿入されませんでした
pMyObj pMyObj.m_i = 2

Coliru のコード

注:この回答に収まるように、説明をできるだけ短くシンプルに保つように努めました. より正確で包括的な説明については、Fluent C++に関するこの記事を読むことをお勧めします。

于 2019-09-26T06:47:05.640 に答える
5

キー0の要素を上書きしたい場合

function[0] = 42;

さもないと:

function.insert(std::make_pair(0, 42));
于 2010-11-26T15:53:12.760 に答える
3

つまり、[]値の型のデフォルト コンストラクターを呼び出して新しい値を代入する必要があるため、値の更新には operator の方が効率的ですが、値の追加には operator の方が効率insert()的です。

Scott Meyers による「Effective STL: 50 Specific Ways to Improvement Your Use of the Standard Template Library 」の項目 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));
    }
}

これのジェネリック プログラミングのないバージョンを選択することもできますが、要点は、このパラダイム (「追加」と「更新」を区別する) が非常に役立つということです。

于 2017-03-02T18:58:07.390 に答える
3

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

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% の時間がかかります。

于 2014-01-13T15:43:13.923 に答える
1

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 );
于 2010-11-26T18:32:34.630 に答える