C ++에서 벡터의 초기 용량


94

이란 무엇입니까 capacity()std::vector기본 constructor에를 사용하여 생성되는가? 나는 그것이 size()0 이라는 것을 압니다 . 기본 생성 벡터가 힙 메모리 할당을 호출하지 않는다고 말할 수 있습니까?

이런 식으로 .NET과 같은 단일 할당을 사용하여 임의 예약으로 배열을 만들 수 있습니다 std::vector<int> iv; iv.reserve(2345);. 어떤 이유로 size()2345 에서 시작하고 싶지 않다고 가정 해 봅시다 .

예 : Linux (g ++ 4.4.5, 커널 2.6.32 amd64)

#include <iostream>
#include <vector>

int main()
{
  using namespace std;
  cout << vector<int>().capacity() << "," << vector<int>(10).capacity() << endl;
  return 0;
}

인쇄 0,10. 규칙입니까, 아니면 STL 공급 업체에 따라 다릅니 까?


7
Standard는 벡터의 초기 용량에 대해 아무것도 지정하지 않지만 대부분의 구현에서는 0을 사용합니다.
Mr.Anubis 2012 년

11
보장 할 수는 없지만 요청하지 않고 메모리를 할당 한 구현의 품질에 대해 심각하게 의문을 제기합니다.
Mike Seymour

2
@MikeSeymour 동의하지 않습니다. 정말 고성능 구현에는 작은 인라인 버퍼가 포함될 수 있습니다.이 경우 초기 capacity ()를 설정하는 것이 합리적입니다.
alastair

6
@alastair swap모든 반복자와 참조를 사용할 때 유효합니다 ( end()s 제외 ). 이는 인라인 버퍼가 불가능 함을 의미합니다.
Notinlist

답변:


75

표준은 capacity컨테이너 의 이니셜 이 무엇인지 지정하지 않으므로 구현에 의존하고 있습니다. 일반적인 구현은 용량을 0에서 시작하지만 보장 할 수는 없습니다. 반면에 당신의 전략을 더 잘할 방법이 std::vector<int> iv; iv.reserve(2345);없으므로 그것을 고수하십시오.


1
나는 당신의 마지막 진술을 사지 않습니다. 처음에 용량이 0이라고 믿을 수 없다면 벡터가 초기 크기를 갖도록 프로그램을 재구성 할 수 있습니다. 이것은 힙 메모리 요청 수의 절반이됩니다 (2에서 1로).
비트 마스크

4
@bitmask : 실용적입니다 : 벡터가 기본 생성자에서 메모리를 할당 하는 구현 에 대해 알고 있습니까? 표준에 의해 보장되지는 않지만 Mike Seymour가 지적했듯이 필요없이 할당을 트리거하면 구현 품질과 관련하여 나쁜 냄새가납니다 .
David Rodríguez-dribeas

3
@ DavidRodríguez-dribeas : 그게 요점이 아닙니다. 전제는 "현재 전략보다 더 잘할 없으므로 어리석은 구현 이 있는지 궁금해하지 마십시오 "였습니다. 전제가 "그런 구현이 없으므로 귀찮게하지 마십시오"라면 나는 그것을 살 것입니다. 결론은 사실이지만 그 의미는 작동하지 않습니다. 미안해, 내가 꼼짝 마 따 먹고 있을지도 몰라.
비트 마스크

3
@bitmask 기본 구성에 메모리를 할당하는 구현이있는 경우, 말한대로 할당 수를 절반으로 줄입니다. 그러나 vector::reserve초기 크기를 지정하는 것과는 다릅니다. 초기 크기 값 / 복사본을 사용하는 벡터 생성자는 n객체를 초기화 하므로 선형 복잡성이 있습니다. OTOH, 예약 호출 은 재 할당이 트리거 된 경우size() 요소의 복사 / 이동만을 의미 합니다. 빈 벡터에는 복사 할 것이 없습니다. 따라서 구현시 기본 생성 벡터에 메모리를 할당하더라도 후자는 바람직 할 수 있습니다.
Praetorian 2012 년

4
@bitmask,이 정도의 할당에 대해 염려한다면 특정 표준 라이브러리의 구현을 살펴보고 추측에 의존하지 않아야합니다.
Mark Ransom 2012 년

37

std :: vector의 저장소 구현은 크게 다르지만 제가 본 모든 것은 0부터 시작합니다.

다음 코드 :

#include <iostream>
#include <vector>

int main()
{
  using namespace std;

  vector<int> normal;
  cout << normal.capacity() << endl;

  for (unsigned int loop = 0; loop != 10; ++loop)
  {
      normal.push_back(1);
      cout << normal.capacity() << endl;
  }

  cin.get();
  return 0;
}

다음 출력을 제공합니다.

0
1
2
4
4
8
8
8
8
16
16

GCC 5.1 및 :

0
1
2
3
4
6
6
9
9
9
13

MSVC 2013에서.


3
이것은 너무 과소 평가 @Andrew
Valentin Mercier

거의 모든 곳에서 속도 목적에 대한 권장 사항은 거의 항상 벡터를 사용하는 것이므로 희소 데이터와 관련된 작업을 수행하는 경우 ...
Andrew

@Andrew는 무엇에서 시작해야 했습니까? 프로그래머가 기본값보다 더 많은 것을 예약하려면 메모리를 할당하고 할당 해제하는 데 시간을 낭비하는 것이 좋습니다. 1로 시작해야한다고 가정한다면 누군가 어쨌든 1을 할당하자마자 할당 할 것입니다.
Puddle

@Puddle 당신은 액면가로 받아들이는 대신 줄 사이를 읽고 있습니다. 비꼬 지 않다는 단서는 "스마트"라는 단어와 희소 데이터를 언급하는 두 번째 의견입니다.
Andrew

@Andrew 오 좋아, 당신은 그들이 0에서 시작했을 정도로 안심했다. 왜 농담으로 그것에 대해 논평 하는가?
Puddle

7

표준을 이해하는 한 (실제로 참조 이름을 지정할 수는 없지만) 컨테이너 인스턴스화 및 메모리 할당은 합당한 이유로 의도적으로 분리되었습니다. 따라서 당신은

  • constructor 컨테이너 자체를 만들려면
  • reserve() 주어진 개체 수를 최소한 (!) 수용하기 위해 적절하게 큰 메모리 블록을 미리 할당합니다.

그리고 이것은 많은 의미가 있습니다. 존재하는 유일한 권리 reserve()는 벡터를 성장시킬 때 비용이 많이 드는 재 할당을 코딩 할 기회를주는 것입니다. 유용하게 사용하려면 저장할 개체의 수를 알고 있거나 최소한 교육 된 추측을 할 수 있어야합니다. 이것이 주어지지 않으면 reserve()낭비되는 메모리에 대한 재 할당을 변경하므로 멀리하는 것이 좋습니다 .

그래서 모두 합치면 :

  • 표준은 의도적 으로 특정 수의 객체에 대해 메모리 블록을 미리 할당 할 수있는 생성자를 지정 하지 않습니다 (이는 구현에 특정한 고정 "무언가"를 할당하는 것보다 적어도 더 바람직 할 것입니다).
  • 할당은 암시 적이 어서는 안됩니다. 따라서 블록을 미리 할당하려면 별도의 호출을 reserve()해야하며 동일한 건설 장소에있을 필요는 없습니다 (수용에 필요한 크기를 알게 된 후 나중에 가능할 수도 있습니다).
  • 따라서 벡터가 항상 구현 정의 된 크기의 메모리 블록을 미리 할당하면의 의도 된 작업을 망칠 reserve()수 있지 않습니까?
  • STL이 의도 한 목적과 벡터의 예상 크기를 자연스럽게 알 수없는 경우 블록을 사전 할당하면 어떤 이점이 있습니까? 비생산적이지 않다면 다소 무의미 할 것입니다.
  • 대신 적절한 해결책은 push_back().NET에 의해 이전에 명시 적으로 할당되지 않은 경우 첫 번째로 특정 블록을 할당하고 구현 하는 것 reserve()입니다.
  • 필요한 재 할당의 경우 블록 크기의 증가도 구현에 따라 다릅니다. 내가 아는 벡터 구현은 크기가 기하 급수적으로 증가하는 것으로 시작하지만 엄청난 양의 메모리를 낭비하거나 심지어 날려 버리는 것을 피하기 위해 특정 최대 값으로 증가율을 제한합니다.

이 모든 것은 할당 생성자에 의해 방해받지 않는 경우에만 완전한 작동과 이점을 제공합니다. 요청시 reserve()(및 shrink_to_fit()) 로 재정의 할 수있는 일반적인 시나리오에 대한 합리적인 기본값이 있습니다 . 따라서 표준이 명시 적으로 명시하지 않더라도 새로 생성 된 벡터가 사전 할당되지 않는 것이 현재의 모든 구현에 대해 매우 안전한 방법이라고 가정합니다.


4

다른 답변에 약간의 추가로 Visual Studio를 사용하여 디버그 조건에서 실행할 때 용량이 0에서 시작하더라도 기본 구성된 벡터가 여전히 힙에 할당된다는 것을 발견했습니다.

특히 _ITERATOR_DEBUG_LEVEL! = 0이면 벡터는 반복기 검사를 돕기 위해 약간의 공간을 할당합니다.

https://docs.microsoft.com/en-gb/cpp/standard-library/iterator-debug-level

당시 사용자 지정 할당자를 사용하고 있었고 추가 할당을 기대하지 않았기 때문에 약간 짜증이났습니다.


흥미롭게도 그들은 noexcept-guarantees (적어도 C + 17의 경우 이전?)를 깨뜨립니다
Deduplicator

4

이것은 오래된 질문이며 여기에있는 모든 답변은 표준의 관점과 std::vector::reserve;

그러나 어떤 STL 구현에서도 std::vector<T>객체 생성시 메모리를 할당하는 것이이치에 맞지 않는지 설명하겠습니다 .

  1. std::vector<T> 불완전한 유형의;

    C ++ 17 이전에는 인스턴스화 시점에서 std::vector<T>의 정의를 T아직 알 수없는 경우 a를 생성하는 것이 정의되지 않은 동작이었습니다 . 그러나 이러한 제약은 C ++ 17에서 완화되었습니다 .

    객체에 메모리를 효율적으로 할당하려면 크기를 알아야합니다. C ++ 17 이상에서 클라이언트는 std::vector<T>클래스가 T. 유형 완전성에 따라 메모리 할당 특성을 갖는 것이 합리적입니까?

  2. Unwanted Memory allocations

    소프트웨어에서 그래프를 모델링해야하는 경우가 많이 있습니다. (나무는 그래프입니다.) 다음과 같이 모델링 할 가능성이 높습니다.

    class Node {
        ....
        std::vector<Node> children; //or std::vector< *some pointer type* > children;
        ....
     };
    

    이제 잠시 생각하고 터미널 노드가 많다고 상상해보십시오. STL 구현이 단순히 .NET Framework에 개체가있을 것으로 예상하여 추가 메모리를 할당하면 매우 화가 날 것입니다 children.

    이것은 하나의 예일뿐입니다.


2

Standard는 용량에 대한 초기 값을 지정하지 않지만 STL 컨테이너는 최대 크기를 초과하지 않는 경우 입력 한만큼의 데이터를 수용하도록 자동으로 커집니다 (알려면 max_size 멤버 함수 사용). 벡터 및 문자열의 경우 더 많은 공간이 필요할 때마다 realloc에 ​​의해 증가가 처리됩니다. 1-1000 값을 갖는 벡터를 만들고 싶다고 가정합니다. 예약을 사용하지 않으면 코드는 일반적으로 다음 루프 동안 2 ~ 18 번의 재 할당이 발생합니다.

vector<int> v;
for ( int i = 1; i <= 1000; i++) v.push_back(i);

예약을 사용하도록 코드를 수정하면 루프 중에 할당이 0이 될 수 있습니다.

vector<int> v;
v.reserve(1000);

for ( int i = 1; i <= 1000; i++) v.push_back(i);

대략적으로 말하면 벡터 및 스트링 용량은 매번 1.5에서 2 배씩 증가합니다.

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