지도에 삽입하는 데 선호되는 / 관용적 인 방법은 무엇입니까?
에 요소를 삽입하는 네 가지 방법을 확인했습니다 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());
기록 된 한 버전 시계 시간 비교를 실행했습니다.
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
참고 :이 답변에 맞게 설명을 가능한 한 짧고 간단하게 유지하려고했습니다. 보다 정확하고 포괄적 인 설명 을 위해 Fluent C ++ 에 대한 이 기사 를 읽는 것이 좋습니다 .
참고 URL : https://stackoverflow.com/questions/4286670/what-is-the-preferred-idiomatic-way-to-insert-into-a-map
'IT' 카테고리의 다른 글
생성자가 @JsonCreator로 주석을 달았을 때 그 인수에 @JsonProperty로 주석을 달아야하는 이유는 무엇입니까? (0) | 2020.09.09 |
---|---|
내 탐색 모음에서 목록 항목의 전체 영역을 링크로 클릭 할 수 있습니까? (0) | 2020.09.09 |
내 웹 사이트의 로고를 브라우저 탭의 아이콘 이미지로 설정해야 할 필요가 있습니까? (0) | 2020.09.09 |
JavaScript의 상속 유형 상속의 좋은 예 (0) | 2020.09.09 |
자바 펼치기 : 그것도 비어 있는지 확인하는 방법? (0) | 2020.09.09 |