C ++ 11에서 표준 라이브러리 컨테이너를 효율적으로 선택하려면 어떻게해야합니까?


135

"C ++ 컨테이너 선택"이라는 잘 알려진 이미지 (치트 시트)가 있습니다. 원하는 사용법에 가장 적합한 컨테이너를 선택하는 순서도입니다.

이미 C ++ 11 버전이 있는지 아는 사람이 있습니까?

이것은 이전 것입니다 : eC ++ 컨테이너 선택


6
전에 본 적이 없습니다. 감사!
WeaselFox

6
@WeaselFox : 이미 C ++-Faq 의 일부입니다 .
Alok 저장

4
C ++ 11은 하나의 새로운 진정한 컨테이너 유형 인 unorder_X 컨테이너 만 소개했습니다. 그것들을 포함하면 해시 테이블이 적절한 지 결정할 때 많은 고려 사항이 있기 때문에 테이블을 상당히 혼란스럽게 할 것입니다.
Nicol Bolas

13
제임스가 옳습니다. 표가 보여주는 것보다 벡터를 사용하는 경우가 더 많습니다. 데이터 로컬 리티의 이점은 많은 경우 일부 작업에서 효율성이 부족합니다 (더 빨리 C ++ 11). 나는 c ++ 03에도 e 차트가 그렇게 유용하지 않다
David Rodríguez-dribeas

33
이것은 귀엽지 만 데이터 구조에 대한 일반적인 교과서를 읽으면 몇 분 안에이 순서도를 다시 만들 수있을뿐만 아니라이 순서도가 빛나는 더 유용한 것들을 알 수있는 상태가 될 것입니다.
앤드류 토마 조스

답변:


97

내가 아는 것은 아니지만 텍스트로 할 수 있다고 생각합니다. 또한 차트는 list일반적으로 그렇게 좋은 컨테이너가 아니기 때문에 차트가 약간 벗어났습니다 forward_list. 두 목록 모두 틈새 응용 프로그램을위한 매우 특수한 컨테이너입니다.

이러한 차트를 작성하려면 두 가지 간단한 지침이 필요합니다.

  • 시맨틱을 먼저 선택
  • 몇 가지 선택이 가능할 때 가장 간단한 방법을 선택하십시오

성능에 대한 걱정은 일반적으로 처음에는 쓸모가 없습니다. 수천 가지 (또는 그 이상)의 아이템을 다루기 시작할 때 큰 O 고려 사항은 실제로 시작됩니다.

컨테이너에는 두 가지 큰 범주가 있습니다.

  • 연관 컨테이너 : find작업이 있습니다
  • 간단한 시퀀스 컨테이너

다음은 그 위에 여러 어댑터를 구축 할 수 있습니다 : stack, queue, priority_queue. 여기서는 어댑터를 그대로두고 인식 할 수있을만큼 충분히 전문화되어 있습니다.


질문 1 : 연관성 ?

  • 하나의 키로 쉽게 검색 해야하는 경우 연관 컨테이너가 필요합니다.
  • 요소를 정렬해야하는 경우 정렬 된 연관 컨테이너가 필요합니다.
  • 그렇지 않으면 질문 2로 이동하십시오.

질문 1.1 : 주문 ?

  • 특정 주문이 필요하지 않은 경우 unordered_컨테이너를 사용하거나 그렇지 않으면 기존 주문을 사용하십시오.

질문 1.2 : 별도의 키 ?

  • 키가 값 map과 다른 경우 a를 사용하고 그렇지 않으면 aset

질문 1.3 : 중복 ?

  • 중복을 유지하려면을 사용하고 multi그렇지 않으면을 사용하십시오 .

예:

고유 한 ID를 가진 사람이 여러 명 있다고 가정하고 가능한 한 간단하게 ID에서 사람 데이터를 검색하려고합니다.

  1. find함수, 따라서 연관 컨테이너를 원합니다.

    1.1. 나는 주문, 그래서 unordered_컨테이너에 대해 덜 신경 쓰지 못했습니다.

    1.2. 내 키 (ID)는 연관된 값과 별개이므로map

    1.3. ID는 고유하므로 복제본이 들어오지 않아야합니다.

최종 답변은 다음과 같습니다 std::unordered_map<ID, PersonData>.


질문 2 : 메모리가 안정적 입니까?

  • 요소가 메모리에서 안정적이어야하는 경우 (즉, 컨테이너 자체가 수정 될 때 움직이지 않아야 함) 일부를 사용하십시오. list
  • 그렇지 않으면 질문 3으로 이동하십시오.

질문 2.1 : 어느 것 ?

  • 에 정착 list; a forward_list는 적은 메모리 풋 프린트에만 유용합니다.

질문 3 : 동적 크기 ?

  • 컨테이너 (컴파일 시간) 알려진 크기를 가지고있는 경우 이 크기는 프로그램의 과정에서 변경되지 않습니다, 그리고 요소가 작도 기본이다 또는 당신이합니다 (사용하여 전체 초기화 목록을 제공 할 수있는 { ... }구문을), 다음을 사용 array. 전통적인 C- 어레이를 대체하지만 편리한 기능으로 대체합니다.
  • 그렇지 않으면 질문 4로 이동하십시오.

질문 4 : 더블 엔드 ?

  • 앞면과 뒷면 모두에서 항목을 제거하려면을 사용하고 deque그렇지 않으면을 사용하십시오 vector.

기본적으로 연관 컨테이너가 필요하지 않은 경우 선택은 vector입니다. 그것은 Sutter and Stroustrup의 권장 사항 이기도 합니다 .


5
+1이지만 몇 가지 참고 사항이 있습니다. 1) array기본 구성 가능한 유형이 필요하지 않습니다. 2) multis를 선택하는 것은 복제가 허용되는 것이 아니라 중요 하게 유지 되는지에 관한 것입니다 (비 multi컨테이너 에 중복을 넣을 수는 있지만 단지 하나만 유지되는 경우).
R. Martinho Fernandes

2
예제는 약간입니다. 1) 비 연관 컨테이너에서 "찾기"(멤버 함수가 아닌 "<알고리즘>"), 1.1) "효율적으로"찾아야하는 경우 unorder_는 O (1)이고 O ( 로그 n).
BlakBat

4
@BlakBat : map.find(key)는 그보다 훨씬 맛 std::find(map.begin(), map.end(), [&](decltype(map.front()) p) { return p.first < key; }));있어서 의미 론적으로 이는의 find함수보다는 멤버 함수입니다 <algorithm>. O (1) 대 O (log n)은 의미에 영향을 미치지 않습니다. 예제에서 "효율적으로"를 제거하고 "쉽게"로 바꾸겠습니다.
Matthieu M.

"요소가 메모리에서 안정적이어야한다면 ...리스트를 사용하십시오" ... 흠, deque이 속성도 가지고 있다고 생각 했습니까?
Martin Ba

@MartinBa : 그렇습니다. A의 deque요소는 안정적 단지 당신이 / 양쪽 끝에 팝업을 밀어 경우; 중간에 삽입 / 삭제를 시작하면 생성 된 간격을 채우기 위해 최대 N / 2 요소가 섞입니다.
Matthieu M.

51

Matthieu의 답변이 마음에 들지만 흐름도를 다음과 같이 다시 설명하겠습니다.

std :: vector를 사용하지 않는 경우

기본적으로 컨테이너가 필요한 경우을 사용하십시오 std::vector. 따라서 다른 모든 컨테이너는에 대한 일부 대체 기능을 제공함으로써 정당화됩니다 std::vector.

생성자

std::vector항목을 뒤섞을 수 있어야하기 때문에 내용물은 움직일 수 있어야합니다. 이는 (주 기본 생성자가되는 내용에 대한 장소 끔찍한 부담하지 않습니다 필요하지 않습니다 에, 감사 emplace등). 그러나 대부분의 다른 컨테이너에는 특정 생성자가 필요하지 않습니다 (다시 감사합니다 emplace). 따라서 이동 생성자 를 절대 구현할 수없는 객체가 있으면 다른 것을 선택해야합니다.

A는 std::deque의 많은 특성을 갖는 일반적으로 대체 될 것입니다 std::vector,하지만 당신은 단지 양단 큐의 하나 끝에 삽입 할 수 있습니다. 가운데 인서트는 움직여야합니다. A는 std::list그 내용에 아무런 요구 사항을 배치하지 않습니다.

부울 필요

std::vector<bool>아니다. 글쎄, 표준입니다. 그러나 일반적으로 허용 vector되는 작업 std::vector은 금지되어 있기 때문에 일반적인 의미 가 아닙니다 . 그리고 그것은 확실히 s를 포함하지 않습니다bool .

따라서 s vector컨테이너에서 실제 동작 이 필요 bool하면에서 가져 오지 않습니다 std::vector<bool>. 따라서으로 마감해야합니다 std::deque<bool>.

수색

당신은 컨테이너의 요소를 찾아야하고, 검색 태그 그냥 인덱스 할 수없는 경우에, 당신은 포기해야 할 수도 있습니다 std::vector에 찬성 set하고 map. 키워드 " may "에 유의하십시오 . 정렬 std::vector은 때때로 합리적인 대안입니다. 또는 Boost.Container 's flat_set/map는 정렬 된을 구현합니다 std::vector.

이제 각각 고유 한 요구가있는 네 가지 변형이 있습니다.

  • map검색 태그가 원하는 항목과 같지 않은 경우를 사용하십시오 . 그렇지 않으면을 사용하십시오 set.
  • 사용 unordered당신이있을 때 많은 절대적으로 할 필요가있는 컨테이너의 항목 및 검색 성능 O(1)보다는 O(logn).
  • multi동일한 검색 태그를 갖는 여러 항목이 필요한 경우 사용하십시오 .

발주

특정 비교 작업을 기준으로 항목 컨테이너를 항상 정렬해야하는 경우을 사용할 수 있습니다 set. 또는 multi_set동일한 값을 갖기 위해 여러 항목이 필요한 경우.

또는 sorted를 사용할 수 std::vector있지만 정렬 상태를 유지해야합니다.

안정

반복자와 참조가 무효화되는 경우가 종종 우려됩니다. 다양한 다른 위치에 해당 항목에 대한 반복자 / 포인터가 있도록 항목 목록이 필요한 경우 std::vector무효화에 대한 접근 방식이 적절하지 않을 수 있습니다. 현재 크기 및 용량에 따라 삽입 작업이 무효화 될 수 있습니다.

std::list반복자 및 관련 참조 / 포인터는 항목 자체가 컨테이너에서 제거 될 때만 무효화됩니다. std::forward_list기억이 심각한 문제라면

보증이 너무 강력 std::deque하면 약하지만 유용한 보증을 제공합니다. 무효화는 중간에 삽입 한 결과이지만 머리 또는 꼬리에 삽입 하면 컨테이너의 항목에 대한 포인터 / 참조가 아닌 반복자 만 무효화 됩니다.

삽입 성능

std::vector 끝 부분에 저렴한 삽입 만 제공합니다 (그러면 용량을 불어도 비용이 많이 듭니다).

std::list성능면에서 비싸지 만 (각각 새로 삽입 된 항목에는 메모리 할당 비용이 들지만) 일관 됩니다. 또한 성능 저하없이 거의 비용을 들이지 않고 아이템을 뒤섞을 수있는 기능을 제공하며 성능 std::list손실없이 동일한 유형의 다른 컨테이너 와 아이템을 교환 할 수 있습니다 . 많은 것을 섞어 야 하는 경우 사용하십시오 std::list.

std::deque머리와 꼬리에 일정한 시간 삽입 / 제거를 제공하지만 중간에 삽입하는 것은 상당히 비쌀 수 있습니다. 따라서 앞면과 뒷면에서 물건을 추가 / 제거 해야하는 경우 필요한 std::deque것일 수 있습니다.

이동 의미론으로 인해 std::vector삽입 성능이 예전만큼 나쁘지 않을 수 있습니다. 일부 구현에서는 이동 시맨틱 기반 항목 복사 (소위 "스왑 타이밍")의 형태를 구현했지만 이제는 이동이 언어의 일부이므로 표준에서 요구합니다.

동적 할당 없음

std::array가능한 적은 동적 할당을 원한다면 훌륭한 컨테이너입니다. C- 어레이를 감싸는 래퍼 일뿐입니다. 즉, 컴파일 타임에 크기를 알아야합니다 . 그걸로 살 수 있다면를 사용하십시오 std::array.

즉 , 크기를 사용 std::vector하고 사용 reserve하면 경계에 대해서도 잘 작동 std::vector합니다. 이 방법으로 실제 크기는 다를 수 있으며 용량을 날리지 않는 한 하나의 메모리 할당 만 얻을 수 있습니다.


1
음, 너무 : WRT는 벡터를 유지하는 많은 정렬 나는 당신의 대답처럼 떨어져에서 std::sort, 또한이 std::inplace_merge쉽게 (라기보다는 새로운 요소를 배치하는 흥미있는 std::lower_bound+의 std::vector::insert호출). 에 대해 배울 수있는 좋은 flat_setflat_map!
Matthieu M.

2
16 바이트로 정렬 된 유형의 벡터도 사용할 수 없습니다. 또한 좋은 교체 vector<bool>입니다 vector<char>.
Inverse

@Inverse : "16 바이트로 정렬 된 유형의 벡터도 사용할 수 없습니다." 누가 그래? std::allocator<T>해당 정렬을 지원하지 않는 경우 (그리고 왜 그렇지 않을지 모르겠다면) 항상 사용자 지정 할당자를 사용할 수 있습니다.
Nicol Bolas

2
@Inverse : C ++ 11 std::vector::resize에는 값을 갖지 않는 과부하가 있습니다 (새로운 크기 만 취하면 새로운 요소가 기본적으로 제 위치에 구성됩니다). 또한 왜 컴파일러가 정렬을 갖도록 선언 된 경우에도 값 매개 변수를 올바르게 정렬 할 수 없습니까?
Nicol Bolas

1
bitset사전에 크기를 알고 있다면 bool을 위해 en.cppreference.com/w/cpp/utility/bitset
bendervader


1

여기에는 빠른 스핀이 있지만 아마도 작업이 필요할 것입니다.

Should the container let you manage the order of the elements?
Yes:
  Will the container contain always exactly the same number of elements? 
  Yes:
    Does the container need a fast move operator?
    Yes: std::vector
    No: std::array
  No:
    Do you absolutely need stable iterators? (be certain!)
    Yes: boost::stable_vector (as a last case fallback, std::list)
    No: 
      Do inserts happen only at the ends?
      Yes: std::deque
      No: std::vector
No: 
  Are keys associated with Values?
  Yes:
    Do the keys need to be sorted?
    Yes: 
      Are there more than one value per key?
      Yes: boost::flat_map (as a last case fallback, std::map)
      No: boost::flat_multimap (as a last case fallback, std::map)
    No:
      Are there more than one value per key?
      Yes: std::unordered_multimap
      No: std::unordered_map
  No:
    Are elements read then removed in a certain order?
    Yes:
      Order is:
      Ordered by element: std::priority_queue
      First in First out: std::queue
      First in Last out: std::stack
      Other: Custom based on std::vector????? 
    No:
      Should the elements be sorted by value?
      Yes: boost::flat_set
      No: std::vector

링크 된 노드가 마음에 들지 않기 때문에 이것이 C ++ 03 버전 과 크게 다르다는 것을 알 수 있습니다 . 링크 된 노드 컨테이너는 드문 경우를 제외하고 일반적으로 링크되지 않은 컨테이너에 의해 성능이 저하 될 수 있습니다. 이러한 상황이 무엇인지 모르고 부스트에 액세스 할 수있는 경우 링크 된 노드 컨테이너를 사용하지 마십시오. (std :: list, std :: slist, std :: map, std :: multimap, std :: set, std :: multiset). 이 목록은 (A) 코드에서 처리하는 것의 99.99 %이며 (B) 많은 요소가 다른 컨테이너가 아닌 사용자 지정 알고리즘을 필요로하기 때문에 주로 소형 및 중간면 컨테이너에 중점을 둡니다.

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