에 요소를 삽입하는 네 가지 방법을 확인했습니다 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
(예 : 추가const
로first_type
)std::pair<int, int>
또한의 템플릿 생성자에 추가 호출을 필요std::pair
로 매개 변수를 변환하기 위해value_type
(예 : 추가const
로first_type
)std::map<int, int>::value_type
insert
멤버 함수에서 예상하는 매개 변수 유형이므로 의심 할 여지가 전혀 없습니다 .
결국 operator[]
기본 생성 및 할당에 추가 비용이없고 mapped_type
새 키가 효과적으로 삽입되었는지 여부를 결정하는 데 신경 쓰지 않는 한, 목적이 삽입 일 때 사용 하는 것을 피할 것입니다 . 를 사용할 때 insert
a를 만드는 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());
답변
키 0으로 요소를 덮어 쓰려면
function[0] = 42;
그렇지 않으면:
function.insert(std::make_pair(0, 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
참고 :이 답변에 맞게 설명을 가능한 한 짧고 간단하게 유지하려고했습니다. 보다 정확하고 포괄적 인 설명 을 위해 Fluent C ++ 에 대한 이 기사 를 읽는 것이 좋습니다 .
답변
위에서 언급 한 버전 간의 시간 비교를 실행했습니다.
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));
}
}
일반 프로그래밍이없는 버전을 선택하기로 결정할 수 있지만 요점은이 패러다임 ( ‘추가’와 ‘업데이트’를 구별)이 매우 유용하다는 것입니다.