IT

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

lottoking 2020. 9. 9. 08:14
반응형

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


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

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


우선, 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아마도 갈 길일 것입니다.


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방법이 일반적으로 그런 방식으로 사용하지 않기 때문에 독자를 놀라게 할 수 있는 것입니다.


첫 번째 버전 :

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

기록 된 한 버전 시계 시간 비교를 실행했습니다.

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 % 더 많은 시간이 걸립니다.


간단히 말해, []연산자는 값 유형의 기본 생성자를 호출 한 다음 새 값을 할당하는 것이 포함되기 때문에 값을 업데이트하는 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));
    }
}

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


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

function[0] = 42;

그렇지 않으면:

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

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

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

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


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 ++ 에 대한 이 기사읽는 것이 좋습니다 .

참고 URL : https://stackoverflow.com/questions/4286670/what-is-the-preferred-idiomatic-way-to-insert-into-a-map

반응형