C ++でのベクトルの初期容量


92

何であるcapacity()std::vectorデフォルトconstuctorを使用して作成されますか?私size()はがゼロであることを知っています。デフォルトの構築されたベクトルはヒープメモリ割り当てを呼び出さないと言うことができますか?

このようにして、のような単一の割り当てを使用して、任意の予約を持つ配列を作成することが可能になります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
標準では、ベクトルの初期容量については何も指定されていませんが、ほとんどの実装では0が使用されます。
Mr.Anubis 2012

11
保証はありませんが、私が要求することなくメモリを割り当てた実装の品質に真剣に疑問を投げかけます。
マイクシーモア

2
@MikeSeymour同意しません。非常に高性能な実装には小さなインラインバッファが含まれている可能性があります。その場合、初期のcapacity()をそれに設定するのが理にかなっています。
alastair 2016

6
@alastairswapすべてのイテレータと参照を使用する場合、有効なままです(end()sを除く)。つまり、インラインバッファは使用できません。
Notinlist 2016

回答:


74

この標準ではcapacity、コンテナのイニシャルを指定していないため、実装に依存しています。一般的な実装では、容量をゼロから開始しますが、保証はありません。一方で、あなたの戦略を改善する方法はありませんstd::vector<int> iv; iv.reserve(2345);


1
私はあなたの最後の声明を買いません。最初に容量が0であると信頼できない場合は、プログラムを再構築して、ベクトルが初期サイズになるようにすることができます。これにより、ヒープメモリ要求の数が半分になります(2から1)。
ビットマスク2012

4
@bitmask:実用的:あなたは知っている任意のベクトルはデフォルトコンストラクタでメモリを割り当てる実装?標準では保証されていませんが、Mike Seymourが指摘しているように、必要なしに割り当てをトリガーすると、実装品質に関して悪臭がします
デビッド・ロドリゲス- dribeas

3
@DavidRodríguez-dribeas:それは重要ではありません。前提は、「現在の戦略よりもうまくやることはできないので、愚かな実装があるのではないかと考えないでください」でした。「そのような実装はないので、気にしないでください」という前提があれば、私はそれを購入します。結論はたまたま真実ですが、その意味は機能しません。申し訳ありませんが、多分私はピッキングをしていません。
ビットマスク2012

3
@bitmaskデフォルトの構築でメモリを割り当てる実装が存在する場合、あなたが言ったことを実行すると、割り当ての数が半分になります。ただしvector::reserve、初期サイズを指定することと同じではありません。初期サイズ値/コピーを取得するベクトルコンストラクターはnオブジェクトを初期化するため、線形の複雑さを持ちます。OTOH、予約の呼び出しは、再割り当てがトリガーされた場合にのみsize()要素のコピー/移動を意味します。空のベクトルでは、コピーするものは何もありません。したがって、実装がデフォルトの構築されたベクトルにメモリを割り当てる場合でも、後者が望ましい場合があります。
プレトリアン2012

4
@bitmask、この程度の割り当てが心配な場合は、推測に頼るのではなく、特定の標準ライブラリの実装を確認する必要があります。
マークランサム2012

36

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

MSVC2013の下で。


3
これは非常に過小評価されています@ Andrew–
Valentin Mercier

速度の目的で推奨されるのはほとんどの場合ベクトルを使用することであることが事実上どこにでもあるので、スパースデータを含む何かをしている場合は...
Andrew

@Andrew彼らは何から始めるべきだったのですか?プログラマーがデフォルトより多くを予約したい場合、何かを割り当てることは、そのメモリーの割り当てと割り当て解除に時間を浪費するだけです。1で始まると想定している場合は、とにかく誰かが1を割り当てるとすぐにそれを割り当てます。
水たまり

@Puddleあなたはそれを額面通りに受け取るのではなく、行の間を読んでいます。それが皮肉ではないという手がかりは、「スマート」という言葉と、スパースデータに言及している私の2番目のコメントです。
アンドリュー

@Andrewああ、あなたは彼らが0から始めたので十分安心しました。なぜ冗談でそれについてコメントするのですか?
水たまり

7

私が標準を理解している限り(実際には参照に名前を付けることはできませんでしたが)、コンテナのインスタンス化とメモリ割り当ては、正当な理由で意図的に分離されています。そのため、あなたは明確な、別々の要求を持っています

  • constructor コンテナ自体を作成するには
  • reserve() 少なくとも(!)指定された数のオブジェクトを収容するために適切に大きなメモリブロックを事前に割り当てる

そして、これは非常に理にかなっています。存在する唯一の権利reserve()は、ベクトルを成長させるときに、おそらく高価な再割り当てをコード化する機会を与えることです。有用であるためには、保存するオブジェクトの数を知っているか、少なくとも知識に基づいた推測ができる必要があります。これが与えられない場合はreserve()、無駄なメモリの再割り当てを変更するだけなので、近づかないほうがよいでしょう。

それで、それをすべてまとめます:

  • この標準では、特定の数のオブジェクトにメモリブロックを事前に割り当てることができるコンストラクターを意図的に指定していませ(これは、実装固有の固定された「何か」を内部で割り当てるよりも少なくとも望ましいでしょう)。
  • 割り当ては暗黙的であってはなりません。したがって、ブロックを事前に割り当てるには、に別の呼び出しを行う必要がありますreserve()、これは同じ建設場所にある必要はありません(収容するために必要なサイズを認識した後、後で行うことができます/もちろんする必要があります)
  • したがって、ベクトルが常に実装で定義されたサイズのメモリブロックを事前に割り当てる場合、これは意図された仕事を失敗させるreserve()でしょう。
  • STLがベクトルの意図された目的と予想されるサイズを自然に知ることができない場合、ブロックを事前に割り当てることの利点は何でしょうか?逆効果ではないにしても、それはかなり無意味です。
  • 代わりに適切な解決策は、最初のブロックを使用して特定のブロックを割り当てて実装push_back()することreserve()です。以前に明示的に割り当てられていない場合は、。
  • 再割り当てが必要な場合、ブロックサイズの増加も実装固有です。私が知っているベクターの実装は、サイズの指数関数的な増加から始まりますが、大量のメモリを浪費したり、それを吹き飛ばしたりすることを避けるために、増分率を特定の最大値に制限します。

これはすべて、割り当てコンストラクターによって妨げられない場合にのみ、完全な操作と利点が得られます。reserve()(およびshrink_to_fit())によってオンデマンドでオーバーライドできる一般的なシナリオには、妥当なデフォルトがあります。したがって、標準で明示的にそのように述べられていない場合でも、新しく構築されたベクトルが事前に割り当てられないことは、現在のすべての実装にとってかなり安全な賭けであると確信しています。


4

他の回答へのわずかな追加として、Visual Studioでデバッグ条件下で実行すると、容量がゼロから始まっても、デフォルトで構築されたベクターがヒープに割り当てられることがわかりました。

具体的には、_ITERATOR_DEBUG_LEVEL!= 0の場合、vectorはイテレータチェックに役立つスペースを割り当てます。

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

当時カスタムアロケーターを使用していて、追加の割り当てを期待していなかったので、これは少し面倒でした。


興味深いことに、彼らはnoexcept-guaranteesを破ります(少なくともC + 17、それ以前?):en.cppreference.com/w/cpp/container/vector/vector
Deduplicator

4

これは古い質問であり、ここでのすべての回答は、標準の観点と、std::vector::reserve;を使用してポータブルな方法で初期容量を取得する方法を正しく説明しています。

ただし、その理由を説明します std::vector<T>オブジェクトの構築時にSTL実装がメモリを割り当てることが意味をなさないします

  1. std::vector<T> 不完全なタイプの;

    C ++ 17より前は、 std::vector<T>Tは、インスタンス化の時点での定義がまだ不明な場合にでした。ただし、その制約は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実装が、にオブジェクトがあることを見越して追加のメモリを割り当てると、非常に腹を立てますchildren

    これはほんの一例です。もっと考えてみてください...


2

標準では容量の初期値は指定されていませんが、最大サイズを超えない限り、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.