지도에 삽입하는 데 선호되는 / 관용적 인 방법은 무엇입니까?


111

에 요소를 삽입하는 네 가지 방법을 확인했습니다 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));

그 중 어느 것이 선호되는 / 관용적 인 방법입니까? (그리고 내가 생각하지 않은 다른 방법이 있습니까?)


26
지도는 "기능"이 아니라 "답변"이라고해야합니다.
Vincent Robert

2
@ 빈센트 : 흠? 함수는 기본적으로 두 세트 사이의 맵입니다.
fredoverflow 2010

7
@FredOverflow은 ... 빈센트의 댓글이 좀 특정 도서에 대한 농담 것 같다
빅터 소로 킨

1
그것은 원본과 모순되는 것 같습니다 .42는 (a) 생명, 우주 및 모든 것에 대한 답이 될 수 없으며 (b) 아무것도 아닙니다. 그렇다면 생명, 우주 및 모든 것을 정수로 어떻게 표현합니까?
Stuart Golodetz 2010

19
@sgolodetz 충분히 큰 int로 모든 것을 표현할 수 있습니다.
Yakov Galka

답변:


90

우선, operator[]insert멤버 함수는 기능적으로 동일하지 않습니다 :

  • operator[]것입니다 검색 키의 삽입 기본 구성 발견되지 않는 경우는 값을, 그리고 당신이 값을 지정하는에 대한 참조를 반환합니다. mapped_type기본 구성 및 할당 대신 직접 초기화되는 이점을 얻을 수 있다면 이것은 비효율적 일 수 있습니다 . 이 방법을 사용하면 삽입이 실제로 발생했는지 또는 이전에 삽입 된 키의 값만 덮어 썼는지 여부를 확인할 수 없습니다.
  • insert멤버 함수는 종종 잊고 있지만, 키가 이미 맵에 존재하는 경우 아무런 영향을 미치지 것, 반환 std::pair<iterator, bool>관심이있을 수있는가 (삽입 실제로 수행 된 경우 특히 결정).

나열된 모든 가능성에서를 호출 insert하면 세 가지 모두 거의 동일합니다. 다시 한번 insert표준 에서 서명을 살펴 보겠습니다 .

typedef pair<const Key, T> value_type;

  /* ... */

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

그렇다면 세 호출은 어떻게 다른가요?

  • std::make_pair템플릿 인수 공제에 의존하고 있었다 (이 경우 것입니다 ) 실제와는 다른 유형의 생산 무언가 value_type에 대한 추가 호출이 필요합니다지도의 std::pair로 변환하기 위해 템플릿 생성자 value_type(예 : 추가 constfirst_type)
  • std::pair<int, int>또한의 템플릿 생성자에 추가 호출을 필요 std::pair로 매개 변수를 변환하기 위해 value_type(예 : 추가 constfirst_type)
  • std::map<int, int>::value_typeinsert멤버 함수에서 예상하는 매개 변수 유형이므로 의심 할 여지가 전혀 없습니다 .

결국 operator[]기본 생성 및 할당에 추가 비용이없고 mapped_type새 키가 효과적으로 삽입되었는지 여부를 결정하는 데 신경 쓰지 않는 한, 목적이 삽입 일 때 사용 하는 것을 피할 것입니다 . 를 사용할 때 inserta를 만드는 value_type것이 아마도 갈 길일 것입니다.


make_pair ()에서 Key에서 const Key로 변환하면 실제로 다른 함수 호출을 요청합니까? 암시 적 캐스트만으로도 어느 컴파일러가 기꺼이 그렇게 할 수있을 것 같습니다.
galactica

99

C ++ 11부터는 두 가지 주요 추가 옵션이 있습니다. 먼저 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방법이 일반적으로 그런 방식으로 사용되지 않기 때문에 독자를 약간 놀라게 할 수 있다는 것입니다.


8
새로운도있다 insert_or_assigntry_emplace
sp2danny

11

첫 번째 버전 :

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이미 맵에있는 경우 아무 작업도 수행하지 마십시오 . 키가 없으면 키 / 값 쌍을 삽입합니다.

세 가지 삽입 기능은 거의 동일합니다. std::map<int, int>::value_type는 IS typedef에 대한 std::pair<const int, int>, 그리고 std::make_pair()분명히을 생산 std::pair<>템플릿 공제 마법을 통해. 그러나 최종 결과는 버전 2, 3 및 4에서 동일해야합니다.

어느 것을 사용할까요? 개인적으로 버전 1을 선호합니다. 간결하고 "자연 스럽다". 그 덮어 쓰기 행동이 요구되지 않는 경우는 단일이 나도 몰라 버전 2와 3 미만의 입력을 필요로하기 때문에 물론, 그때는 버전 4를 선호 사실상 에 키 / 값 쌍을 삽입하는 방법 std::map.

생성자 중 하나를 통해 맵에 값을 삽입하는 또 다른 방법 :

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

5

키 0으로 요소를 덮어 쓰려면

function[0] = 42;

그렇지 않으면:

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

5

C ++ 17 std::map 은 두 가지 새로운 삽입 방법을 제공 하기 때문에 sp2danny주석 에서도 언급했듯이 insert_or_assign()및 .try_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 ++ 에 대한 이 기사 를 읽는 것이 좋습니다 .


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

2198ms, 2078ms, 2072ms

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

2290ms, 2037ms, 2046ms

 map_W_3[it] = Widget(2.0);

2592ms, 2278ms, 2296ms

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

2234ms, 2031ms, 2027ms

따라서 서로 다른 인서트 버전 간의 결과는 무시 될 수 있습니다 (가설 ​​테스트를 수행하지 않음)!

map_W_3[it] = Widget(2.0);버전은 위젯에 대한 기본 생성자를 사용한 초기화로 인해이 예제에서 약 10-15 % 더 많은 시간이 걸립니다.


2

간단히 말해, []연산자는 값 유형의 기본 생성자를 호출 한 다음 새 값을 할당하는 것이 포함되기 때문에 값을 업데이트하는 insert()데 더 효율적이며 값을 추가 하는 데 더 효율적입니다.

효과적인 STL 에서 인용 된 스 니펫 : Scott Meyers 의 표준 템플릿 라이브러리 사용을 개선하는 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));
    }
}

일반 프로그래밍이없는 버전을 선택하기로 결정할 수 있지만 요점은이 패러다임 ( '추가'와 '업데이트'를 구별)이 매우 유용하다는 것입니다.


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

1

삽입에 대한 또 다른 관심을 보여주기 위해 문제 (문자열 맵)를 약간 변경합니다.

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;"에 오류를 표시하지 않는다는 사실 엄청난 영향을 미칠 수 있습니다!


컴파일러는 std::string::operator=(char)존재 하기 때문에 전자에 대한 오류를 표시 std::string::string(char)하지 않지만 생성자 가 존재하지 않기 때문에 후자에 대해서는 오류를 표시 합니다. 그것은 C ++에서 항상 자유롭게 정수 스타일의 문자를 해석하기 때문에 오류가 발생 char이 컴파일러의 버그가 아니라, 대신 프로그래머 오류입니다 그래서. 기본적으로 코드에 버그가 발생하는지 여부는 스스로주의해야 할 사항입니다. BTW, 인쇄 할 수 있으며 rancking[0]ASCII를 사용하는 컴파일러 *(char)(42).
Keith M

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.