web-dev-qa-db-ja.com

なぜstd :: vectorのリザーブは容量を「2倍」にしないのに、サイズ変更はしますか?

_std::vector<T>::resize_は、現在のサイズを超える1つの要素にサイズを変更した場合でも、容量が「2倍になる」ことがわかりました。

_std::vector<int> v(50);
v.resize(51);
std::cout << v.capacity() << std::endl;
_

このプログラムは、GCCおよびClangでは100、Visual C++では75を出力します。ただし、resizeからreserveに切り替えると:

_std::vector<int> v(50);
v.reserve(51);
std::cout << v.capacity() << std::endl;
_

出力は、3つのコンパイラすべてで51です。

実装がresizereserveに異なる展開戦略を使用する理由を疑問に思います。一貫性がないようで、ここでも同じ動作を期待します。


パフォーマンスへの影響が報告されている質問の動機へのリンクを追加しています:C++ STLベクターが多くの予約を行うと1000倍遅いのはなぜですか?


reserveの要件を明確にするために、C++ 11標準から引用を追加します。 §23.3.6.3(2):

reserve()の後、capacity()は、再割り当ての場合、reserveの引数に対して以上起こる...


その他の考え:C++ 11標準から:

複雑さ:挿入される要素の数とベクトルの終わりまでの距離が複雑になります。

これは、事実上、最後に単一の要素を挿入するための一定の(償却された)複雑さを意味します。ただし、これは_Push_back_やinsert(§23.3.6.5)などのvector修飾子にのみ適用されます。

resizeは修飾子の中にリストされていません。 §23.3.6.3vector capacityセクションにリストされています。また、resizeには複雑さの要件はありません。

ただし、vector概要セクション(§23.3.6.1)には次のように書かれています。

it(vector)は、最後に(償却)一定時間の挿入および消去操作をサポートします

問題は、resize(size()+1) "最後に挿入"と見なされるかどうかです。

25
Daniel Langr

私の知る限り、resizereserveも動作を示す必要はありません。ただし、両方ともそのような動作が許可されていますが、どちらも正確な量を割り当てることができ、標準に関する限り、両方とも前の割り当てを増やすことができます。

各割り当て戦略にはそれぞれ利点があります。正確な量を割り当てることの利点は、最大割り当てが事前にわかっている場合、メモリオーバーヘッドがないことです。乗算の利点は、挿入終了操作と混合しても一定の償却プロパティが維持されることです。

テストされた実装によって選択されたアプローチには、サイズ変更時に両方の戦略を許可するという利点があります。 1つの戦略を使用するには、予約してサイズを変更します。もう一方を使用するには、サイズを変更します。もちろん、これを利用するには不特定の動作に注意する必要があります。この利点は、これらの実装を選択した理由である場合とそうでない場合があります。

標準で指定されているように、意図した再割り当て動作を表現できないことが(ベクターによって保証されている方法で)ベクトルAPIの障害と見なされる場合があります。

17
eerorika

resizeがキャパシティを超えている場合、適切なキャパシティを予約したくないことをすでに「実証」しています。一方、reserveを使用する場合は、適切な容量を明示的に要求します。 reserveresizeと同じ戦略を使用する場合、適切な量を予約する方法はありません。

この意味で、resizeのないreserveは、怠zyなもののため、または予約する正確な量がわからない場合のためにあります。必要な容量がわかっている場合は、reserveを呼び出します。それは2つの異なるシナリオです。

PS:StoryTellerが指摘したように、reserveも、標準に従って求められている正確な金額を予約する必要はありません。それにもかかわらず、私は主な議論がまだ残っていると思います:resizereserveなし)とreserveは、あなたがどれだけ予約したいかというヒントを与えるか、実際の容量を気にせず、希望するサイズのコンテナ。

なぜあなたは彼らが同じように振る舞うことを期待しますか? reserveは、後で使用するスペースを事前に割り当てるために使用され、ユーザーがコンテナの予想される最終サイズに適切なハンドルを持っていることを期待します。 resizeは通常の割り当てであるため、コンテナの割り当てられたスペースを幾何学的に増やすという通常の速度効率のよいアプローチに従います。

コンテナは、必要な割り当ての数を減らすための乗算ステップによってサイズが増加するため、速度を維持し、メモリの断片化を減らします。倍増が最も一般的ですが、実装によっては、1.5の手順(MSVCなど)を使用して、各コンテナ内の無駄なスペースを減らすために、割り当てを増やします。

しかし、ユーザーが既にライブラリーにコンテナーがどれだけ大きくなると思うかを伝えている場合-reserveを引き起こすことにより-余分なスペースを割り当てる必要がなく、代わりにユーザーが正しい番号で呼び出すことを信頼できます。 reserveではなく、resizeが異常な動作をします。

6
Jack Aidley

resizeは、その複雑さの保証(要素数の線形挿入)を満たすために、指数再配置戦略に従う必要があります。これは、resize(size() + 1)が一定の複雑さを償却するために必要であり、Push_back(一定の複雑な償却額)が指数関数的に増加するのと同じ理由で指数関数に従う必要があることを考えるとわかります。

reserveの実装は、要素の数が線形であることが唯一の複雑さの要件であるため、好きな割り当て戦略に従うことが許可されていますpresent。ただし、実装が次の2のべき乗に切り上げると、必要なメモリ量をユーザーが正確に知っている場合、これはスペース効率が悪く(驚くべきことです)、ユーザーがこの動作に依存するようになると移植が複雑になる可能性があります。標準の緯度は、スペースの非効率性がない場合、たとえば、アロケーターがその粒度で動作する場合、割り当てをWordサイズに切り上げます。

6
ecatmur

reserveは容量を変更し、resizesizeを変更します。

capacityは、コンテナが現在スペースを割り当てている要素の数です。

sizeは、コンテナ内の要素の数です。

空のベクトルを割り当てると、デフォルトのcapacity(別名スペース)が取得されます。サイズはまだ0であり、要素をベクターに追加すると、サイズが増加します。サイズが容量と等しくなり、アイテムを追加する場合、容量を増やす必要があります(通常は2倍になります)。

ベクトルの問題は、シーケンシャルメモリを確保することです。つまり、古い割り当てられたメモリ領域に新しい割り当てサイズ用のスペースがなかった場合、新しい割り当てを増やすたびに、新しい割り当てへの前の割り当てのコピーも必要になります。

ベクトルの最大要素がわかっている場合、reserveが役立ちます。 reserveを使用すると、予約されたアイテムを渡さない限り、割り当ては1つだけでメモリコピーはありません。

予約数を正確に伝えると、要求した正確なメモリが得られます。要素を追加するだけの場合(サイズ変更しても、アイテムを追加しないとは言わない)。

1
SHR