STL 맵에서 map :: insert를 사용하는 것이 []보다 낫습니까?


201

얼마 전에 저는 STL 맵에 값을 삽입하는 방법에 대해 동료와 토론했습니다 . 나는 map[key] = value; 그것이 자연스럽고 읽기 쉽기 때문에 선호했습니다. map.insert(std::make_pair(key, value))

나는 방금 그에게 물었고 우리 중 어느 쪽도 insert가 더 나은 이유를 기억할 수는 없지만 그것이 스타일 선호가 아니라 효율성과 같은 기술적 이유가 있다고 확신합니다. SGI의 STL 참조는 단순히 "엄밀히 말하면이이 멤버 함수가 필요하지 않습니다. : 그것은 단지 편의를 위해 존재한다"라는

아무도 그 이유를 말해 줄 수 있습니까, 아니면 하나가 있다고 꿈꾸고 있습니까?


2
모든 훌륭한 답변에 감사드립니다-그들은 정말 도움이되었습니다. 이것은 최상의 스택 오버플로 데모입니다. 나는 어떤 대답을 받아 들여야하는지 찢어졌다. netjeff는 다른 행동에 대해 더 명확하다고 Greg Rogers는 성능 문제를 언급했다. 둘 다 체크 할 수 있으면 좋겠다.
danio December

6
실제로 C ++ 11에서는 이중 구성을 피하는 map :: emplace 를 사용하는 것이 가장 좋습니다.
einpoklum

@einpoklum : 사실, Scott Meyers는 그의 연설에서 "유효한 C ++에 대한 진화하는 검색"을 제안합니다.
Thomas Eding

3
@einpoklum : 새로 구성된 메모리에 넣을 때입니다. 그러나지도에 대한 일부 표준 요구 사항으로 인해 삽입이 삽입보다 속도가 느릴 수있는 기술적 이유가 있습니다. 이 링크는 youtube.com/watch?v=smqT9Io_bKo @ ~ 38-40 분과 같은 YouTube에서 자유롭게 이용할 수 있습니다 . SO 링크의 경우 여기 stackoverflow.com/questions/26446352/…
Thomas Eding

1
나는 실제로 마이어스가 제시 한 것에 대해 논쟁 할 것이지만, 그것은이 논평 스레드의 범위를 넘어서고 어쨌든, 나는 나의 이전 의견을 철회해야한다고 생각한다.
einpoklum 1

답변:


240

당신이 쓸 때

map[key] = value;

이 같은 경우 알 수있는 방법은 없습니다 교체value 대한이 key, 또는 당신이 경우 생성 된 새로운를 key함께 value.

map::insert() 만 만들 것입니다 :

using std::cout; using std::endl;
typedef std::map<int, std::string> MyMap;
MyMap map;
// ...
std::pair<MyMap::iterator, bool> res = map.insert(MyMap::value_type(key,value));
if ( ! res.second ) {
    cout << "key " <<  key << " already exists "
         << " with value " << (res.first)->second << endl;
} else {
    cout << "created key " << key << " with value " << value << endl;
}

대부분의 내 앱에서는 일반적으로 생성 또는 교체 여부를 신경 쓰지 않으므로 더 읽기 쉽습니다 map[key] = value.


16
map :: insert는 값을 대체하지 않습니다. 그리고 일반적인 경우 에는 두 번째 경우 (res.first)->second대신 사용하는 것이 좋습니다 value.
dalle

1
map :: insert가 대체되지 않는다는 것을 더 명확하게 업데이트했습니다. 반복자보다 else사용 value이 더 명확 하다고 생각하기 때문에 떠났습니다 . 값의 유형에 특이한 사본 ctor 또는 op ==가있는 경우에만 값이 다르고 해당 유형은 map과 같은 STL 컨테이너를 사용하는 다른 문제를 일으킬 수 있습니다.
netjeff

1
map.insert(std::make_pair(key,value))이어야합니다 map.insert(MyMap::value_type(key,value)). 에서 반환 된 유형이 사용 된 유형 make_pair과 일치하지 않으며 insert현재 솔루션에서 변환이 필요합니다
David Rodríguez-dribeas

1
을 삽입했거나 할당 한 경우 알 수있는 방법이 있습니다 operator[]. 전후의 크기를 비교하면됩니다. map::operator[]기본 구성 가능한 형식에 대해서만 호출 할 수있는 Imho 가 훨씬 더 중요합니다.
idclev 463035818

@ DavidRodríguez-dribeas : 좋은 제안, 나는 대답에서 코드를 업데이트했습니다.
netjeff

53

맵에 이미 존재하는 키와 관련하여 두 가지 의미가 다릅니다. 그래서 그들은 실제로 직접 비교할 수 없습니다.

그러나 operator [] 버전은 기본적으로 값을 구성한 다음 할당하는 것이 필요하므로 복사 비용보다 비싸면 비용이 더 많이 듭니다. 때로는 기본 구성이 의미가 없으므로 operator [] 버전을 사용하는 것이 불가능합니다.


1
make_pair에는 복사 생성자가 필요할 수 있습니다. 기본 생성자보다 더 나쁩니다. 어쨌든 +1.

1
가장 중요한 것은 말했듯이 서로 다른 의미가 있습니다. 따라서 다른 것보다 낫지는 않습니다. 필요한 것을 수행하는 것을 사용하십시오.
jalf

복사 생성자가 기본 생성자보다 할당이 뒤 따르는 이유는 무엇입니까? 그렇다면 클래스를 작성한 사람이 무언가를 놓친 것입니다. operator =는 무엇이든 복사 생성자에서 동일한 작업을 수행했기 때문입니다.
스티브 제섭

1
때로는 기본 구성이 할당 자체만큼 비쌉니다. 당연히 과제와 사본 구성은 동일합니다.
Greg Rogers

@Arkadiy 최적화 된 빌드에서 컴파일러는 종종 많은 불필요한 복사 생성자 호출을 제거합니다.
개미

35

주목해야 할 또 다른 사항 std::map:

myMap[nonExistingKey];지도 nonExistingKey에 기본값 으로 초기화 된 새 항목을 만듭니다 .

이것은 내가 그것을 처음 보았을 때 지옥에서 나에게 두려움을주었습니다. 예상하지 못했을 것입니다. 나에게는 그것이 get 작업처럼 보이며 나는 "부작용"을 기대하지 않았다. 취하다map.find()지도에서 나올 때 하십시오.


3
해시 맵은이 형식에 매우 보편적이지만 괜찮은 견해입니다. 그들은 같은 규칙을 사용하는 방법 때문에 "아무도 이상하다고 생각하지 않는 이상한 것들"중 하나 일 수 있습니다.
Stephen J

19

기본 생성자의 성능 적중이 문제가되지 않으면 신을 사랑하기 위해 더 읽기 쉬운 버전으로 이동하십시오.

:)


5
둘째! 이것을 표시해야합니다. 너무 많은 사람들이 나노초의 속도 향상을 위해 남용과 맞서 싸우고 있습니다. 그런 잔혹성을 유지해야하는 가난한 영혼들을 우리에게 자비를 베푸십시오!
Mr.Ree

6
그렉 로저스 (Greg Rogers)는 다음과 같이 썼다.
dalle

이것은 오래된 질문과 답변입니다. 그러나 "더 읽기 쉬운 버전"은 어리석은 이유입니다. 가장 읽기 쉬운 것은 사람에 따라 다릅니다.
vallentin 2016 년

14

insert 예외 안전의 관점에서 더 좋습니다.

이 표현 map[key] = value은 실제로 두 가지 연산입니다.

  1. map[key] -기본값으로지도 요소를 만듭니다.
  2. = value -해당 요소에 값을 복사합니다.

두 번째 단계에서 예외가 발생할 수 있습니다. 결과적으로 작업은 부분적으로 만 수행됩니다 (새 요소가 맵에 추가되었지만 해당 요소는로 초기화되지 않았습니다 value). 작업이 완료되지 않았지만 시스템 상태가 수정 된 상황을 "부작용"이있는 작업이라고합니다.

insert작업은 강력한 보증을 제공하므로 부작용이 없습니다 ( https://en.wikipedia.org/wiki/Exception_safety ). insert완전히 완료되었거나 맵을 수정되지 않은 상태로 둡니다.

http://www.cplusplus.com/reference/map/map/insert/ :

단일 요소를 삽입하는 경우 예외가 발생할 경우 컨테이너가 변경되지 않습니다 (강력 보장).


1
더 중요한 것은 insert는 기본적으로 구성 가능한 값을 요구하지 않습니다.
UmNyobe

13

응용 프로그램이 속도가 중요한 경우 [] 연산자를 사용하여 조언합니다. 원래 객체의 총 3 복사본을 생성하고 그중 2 개는 임시 객체이며 조만간 파괴됩니다.

그러나 insert ()에서 원래 오브젝트의 사본 4 개가 작성되고 그 중 3 개는 임시 오브젝트 (필수 "임시"일 필요는 없음)이며 파괴됩니다.

1. 한 개의 객체 메모리 할당 2. 한 개의 추가 생성자 호출 3. 한 개의 추가 소멸자 호출 4. 한 개의 개체 메모리 할당 해제

객체가 크면 생성자가 일반적이며 소멸자는 많은 리소스 확보를 수행합니다. 가독성에 관해서는, 나는 둘 다 공평하다고 생각합니다.

같은 질문이 내 마음에 들었지만 가독성이 아니라 속도입니다. 여기 내가 언급 한 요점에 대해 알게 된 샘플 코드가 있습니다.

class Sample
{
    static int _noOfObjects;

    int _objectNo;
public:
    Sample() :
        _objectNo( _noOfObjects++ )
    {
        std::cout<<"Inside default constructor of object "<<_objectNo<<std::endl;
    }

    Sample( const Sample& sample) :
    _objectNo( _noOfObjects++ )
    {
        std::cout<<"Inside copy constructor of object "<<_objectNo<<std::endl;
    }

    ~Sample()
    {
        std::cout<<"Destroying object "<<_objectNo<<std::endl;
    }
};
int Sample::_noOfObjects = 0;


int main(int argc, char* argv[])
{
    Sample sample;
    std::map<int,Sample> map;

    map.insert( std::make_pair<int,Sample>( 1, sample) );
    //map[1] = sample;
    return 0;
}

insert () 사용시 출력 [] 연산자 사용시 출력


4
이제 전체 최적화를 사용하여 해당 테스트를 다시 실행하십시오.
antred

2
또한 연산자 []의 실제 기능을 고려하십시오. 먼저 지정된 키와 일치하는 항목을 맵에서 검색합니다. 하나를 찾으면 해당 항목의 값을 지정된 값으로 덮어 씁니다. 그렇지 않은 경우 지정된 키와 값으로 새 항목을 삽입합니다. 지도가 커질수록 연산자 []에서지도를 검색하는 데 시간이 더 걸립니다. 어떤 시점에서 이것은 여분의 복사 호출을 보완하는 것 이상입니다 (컴파일러가 최적화 마법을 수행 한 후에도 최종 프로그램에 머무를 경우).

1
@antred insert는 동일한 검색을 수행해야 []하므로 맵 키가 고유하기 때문에 차이가 없습니다 .
Sz.

출력물에 무슨 일이 일어나고 있는지에 대한 멋진 그림입니다. 그러나이 2 비트 코드는 실제로 무엇을하고 있습니까? 왜 원본 물체의 사본 3 개가 필요한가요?
rbennett485

1. 할당 연산자를 구현해야합니다. 그렇지 않으면 소멸자에 잘못된 숫자가 표시됩니다. 2. 과도한 복사 구성 "map.insert (std :: make_pair <int, Sample> (1, std : : move (sample))); "
Fl0

10

이제 c ++ 11에서 STL 맵에 쌍을 삽입하는 가장 좋은 방법은 다음과 같습니다.

typedef std::map<int, std::string> MyMap;
MyMap map;

auto& result = map.emplace(3,"Hello");

결과 와 쌍 될 것입니다 :

  • 첫 번째 요소 (result.first)는 삽입 된 쌍을 가리 키거나 키가 이미 존재하는 경우이 키를 가진 쌍을 가리 킵니다.

  • 두 번째 요소 (result.second), 삽입이 정확하거나 거짓이면 무언가 잘못되었습니다.

추신 : 주문에 대해 사건이 없다면 std :: unordered_map;)

감사!


9

map :: insert ()가있는 열쇠는 키가 이미 맵에 있으면 값을 대체하지 않는다는 것입니다. Java 프로그래머가 insert ()가 값이 바뀌는 Java의 Map.put ()과 동일한 방식으로 작동한다고 예상 한 C ++ 코드를 보았습니다.


2

참고로 Boost.Assign 을 사용할 수도 있습니다 .

using namespace std;
using namespace boost::assign; // bring 'map_list_of()' into scope

void something()
{
    map<int,int> my_map = map_list_of(1,2)(2,3)(3,4)(4,5)(5,6);
}

1

여기에 또 다른 예는이 보여입니다 operator[] 덮어 존재하는 경우 키 값을하지만 .insert 덮어 쓰지 않습니다 존재하는 경우 값을.

void mapTest()
{
  map<int,float> m;


  for( int i = 0 ; i  <=  2 ; i++ )
  {
    pair<map<int,float>::iterator,bool> result = m.insert( make_pair( 5, (float)i ) ) ;

    if( result.second )
      printf( "%d=>value %f successfully inserted as brand new value\n", result.first->first, result.first->second ) ;
    else
      printf( "! The map already contained %d=>value %f, nothing changed\n", result.first->first, result.first->second ) ;
  }

  puts( "All map values:" ) ;
  for( map<int,float>::iterator iter = m.begin() ; iter !=m.end() ; ++iter )
    printf( "%d=>%f\n", iter->first, iter->second ) ;

  /// now watch this.. 
  m[5]=900.f ; //using operator[] OVERWRITES map values
  puts( "All map values:" ) ;
  for( map<int,float>::iterator iter = m.begin() ; iter !=m.end() ; ++iter )
    printf( "%d=>%f\n", iter->first, iter->second ) ;

}

1

이것은 다소 제한된 경우이지만, 내가받은 의견에서 판단하면 주목할 가치가 있다고 생각합니다.

과거에 사람들이지도를 사용하는 모습을

map< const key, const val> Map;

실수로 값을 덮어 쓰는 경우를 피하고 다른 코드 비트를 작성하십시오.

const_cast< T >Map[]=val;

내가 기억하는 것처럼이 작업을 수행 한 이유는 이러한 특정 코드에서 맵 값을 덮어 쓰지 않을 것이기 때문입니다. 따라서보다 '읽기 쉬운'방법으로 진행하십시오.[] .

나는 실제로이 사람들이 작성한 코드에서 직접적인 문제를 겪은 적이 없지만, 오늘날까지 쉽게 피할 수있을 때 위험을 감수해서는 안된다고 생각합니다.

덮어 쓰지 않아야 하는 맵 값을 처리하는 경우을 사용하십시오 insert. 가독성을 위해 예외를 두지 마십시오.


여러 사람들이 저것을 썼습니까? 확실히insert (not input)을 사용 const_cast하면 이전 값을 덮어 쓰게되므로 매우 일관성이 없습니다. 또는 값 유형을로 표시하지 마십시오 const. (그런 종류의 일은 대개의 최종 결과 const_cast이므로 거의 항상 다른 곳에 오류를 나타내는 빨간색 플래그입니다.)
Potatoswatter

@ Potatoswatter 당신이 맞아요. const_cast []가 특정 코드 비트에서 오래된 값을 대체하지 않을 것이라고 확신 할 때 const map 값과 함께 const 맵 값을 사용한다는 것을 알았습니다. [] 자체가 더 읽기 쉽기 때문입니다. 내 대답의 마지막 부분에서 언급했듯이 insert값을 덮어 쓰지 않으려는 경우에 사용 하는 것이 좋습니다 . (그냥은 변경 inputinsert- 감사)
dk123

@Potatoswatter 올바르게 기억한다면 사람들이 사용하는 주된 이유 const_cast<T>(map[key])는 1입니다. []는 더 읽기 쉽고, 2. 코드의 특정 비트에 대해 자신이 값을 덮어 쓰지 않을 것이라고 확신합니다. 다른 비트의 알 수없는 코드가 값을 덮어 쓰길 원합니다 const value.
dk123

2
나는 이것이 행해지는 것을 들어 본 적이 없다. 이거 어디서 봤어? 글쓰기 const_cast는 추가 "가독성"을 부정하는 것 이상으로 보이며 []이러한 종류의 자신감은 개발자를 해고하기에 충분한 근거입니다. 까다로운 런타임 조건은 직감이 아닌 방탄 디자인으로 해결됩니다.
Potatoswatter

@ Potatoswatter 교육 게임을 개발 한 과거의 일 중 하나였던 것을 기억합니다. 사람들이 습관을 바꾸기 위해 코드를 작성하도록 할 수 없었습니다. 당신은 절대적으로 맞고 나는 당신에게 강력하게 동의합니다. 귀하의 의견에서, 나는 이것이 원래의 대답보다 주목할 가치가 있다고 결정했으며, 따라서 이것을 반영하도록 업데이트했습니다. 감사!
dk123

1

std :: map insert()함수가 키와 관련된 값을 덮어 쓰지 않는다는 사실은 다음과 같이 객체 열거 코드를 작성할 수 있습니다.

string word;
map<string, size_t> dict;
while(getline(cin, word)) {
    dict.insert(make_pair(word, dict.size()));
}

고유하지 않은 다른 객체를 0..N 범위의 일부 id에 매핑해야 할 때 매우 일반적인 문제입니다. 이 ID는 나중에 예를 들어 그래프 알고리즘에서 사용할 수 있습니다. operator[]내 의견 으로 는 대안이 읽기 쉽지 않을 것입니다.

string word;
map<string, size_t> dict;
while(getline(cin, word)) {
    size_t sz = dict.size();
    if (!dict.count(word))
        dict[word] = sz; 
} 
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.