リーダブルコードという本でSwap技法なるものを知りました。
調べてみるとvector型でClearを呼ぶ際にメモリが正常に開放されないからSwapを使うというものらしいです。
ただそれってvectorにあるeraseをfor文で回すのと変わらなくないかと思っています。
swapによるメモリ解放とeraseによるメモリ開放違いってなんなんでしょうか?
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答4件
0
ベストアンサー
swap技法は今から約10年前に過去の産物となった技法です。他の方の指摘の通りshrink_to_fit
を使いましょう。いずれもあまりに巨大に確保されたcapacityを適切なサイズに落とすことを要求する(実際にそうなるとは言っていない)ものです。
古い本のベストプラクティスとして紹介されている技法はとくにC++11の登場によって不要になるかバッドプラクティスとなっているものが多いので、適用してみようとする前にその技法が今どういう評価を受けているかは慎重に調べることをおすすめします。
追記
cpp
class A
A *a = new A();
vector<A> avec;
a.push_buck(a);
このavecを開放しようとなると ```cpp
delete a;
shrink_to_fit();
と呼ばないと全メモリは開放されない感じなんですかね? aで確保したアドレスとavecに挿入後のアドレスは変らないようですが
待ってください、そのコードはだいたい全部おかしいです。
型の問題
変数avec
の型はstd::vector<A>
です。ですから要素の型はA
です。A*
ではありません。したがってa.push_buck(a);
はコンパイルエラーになります。
std::vector
のデータ構造について
swap技法にしろshrink_to_fit
にしろ、何を目的としたものなのか把握されていないように思います。間違っても全メモリーを開放することを目的としたものではありません。
std::vector
はどのようにメモリーを使うか理解していないのではありませんか?
std::vector
を単純化すると次のような構造体になります(現実にはアロケータがここに加わりますが割愛します)。
cpp
1template<typename T> 2struct vec { 3 T* p; 4 std::size_t size; 5 std::size_t capacity; 6};
capacity
は確保されているメモリー領域の大きさ、size
は格納されている要素数です。つねにsize <= capacity
の関係になります。
例えばstd::vector::push_back
のようなメンバ関数を呼び出すとき、もしsize == capacity
の状態にあると、新たな要素を追加することはできません。そこでメモリーを新規に確保して(一般にcapacity
の1.5~2倍)、すでにある要素をそちらに移して(厳密にはmove操作が無例外で行えるならmove, さもなくばcopy)、今まで割り当てていたメモリーを開放し、capacity
を更新して、末尾に新規要素を追加し、size
を更新します。
vectorに様々な操作をするにつれて、時としてcapacity
がsize
よりも遥かに大きい状態になることがあります。例えば要素の大量削除をしたときなどです。このとき、実行速度を犠牲にしてでも(一般にメモリーの確保作業にはとても時間がかかります)メモリー使用量を削減したいという需要が発生します。この需要を満たすための手段がかつてのswap技法や、std::vector::shrink_to_fit
の呼び出しです。
ここでお示しのコードを上述の型の問題を解決するように書き直してみましょう。
cpp
1class A; 2 3void foo() 4{ 5 A* a = new A(); 6 std:vector<A*> v; 7 v.push_back(a); 8 delete a; 9 a.shrink_to_fit(); 10}
さて、変数a
はA*
型の変数で、new演算子によって割り当てられたメモリーを指し示すポインタです。
これを変数v
のvectorに追加します。つまりv.push_back(a);
の直後ではnew演算子によって割り当てられたメモリーを指し示しているポインタは、a
とv[0]
の2つ存在しています。
delete a;
については一度飛ばします。
では、a.shrink_to_fit();
の意味をここで考えましょう。変数v
、つまりvectorには要素が追加されるだけで削除はしていません。このときcapacity
がsize
よりも遥かに大きい状態になることは起こりえません。するとa.shrink_to_fit();
は無意味ということが言えます。
SHOMIさんやepistemeさんの回答にあるサンプルコードをよく見てください。
## dangling pointer
delete a
について考えます。ここでnew演算子によって割り当てられたメモリーが開放されます。つまりa
とv[0]
は存在しない領域へのポインタになったわけです。存在しない領域へのポインタのことを一般にdangling pointerと呼びます。例えばこのあとに*v[0]
とかやるとdangling pointerをdereferenceしていますから未定義動作となりコンパイラはタイムトラベルしたり鼻から悪魔を召喚したりする可能性がありえます。
投稿2020/07/15 08:22
編集2020/07/16 09:17総合スコア5852
0
vectorにあるeraseをfor文で回すのと変わらなくないかと思っています。
swapによるメモリ解放とeraseによるメモリ開放違いってなんなんでしょうか?
erase()
やclear()
ではvector
が確保したメモリは解放されません。
C++11以降ならerase()
やclear()
の後にshrink_to_fit()を呼ぶことでも解放するよう要求できます。(解放される保証はないですが)
C++
1#include<iostream> 2#include<vector> 3 4int main() 5{ 6 std::vector<char> v; 7 std::cout << "Initial:" << v.capacity() << std::endl; 8 9 v.resize(10000); 10 std::cout << "resize:" << v.capacity() << std::endl; 11 12 while (!v.empty()) 13 { 14 v.erase(v.begin()); 15 } 16 std::cout << "erase all:" << v.capacity() << std::endl; 17 18 std::vector<char>().swap(v); 19 std::cout << "swap:" << v.capacity() << std::endl; 20 21 v.resize(10000); 22 std::cout << "resize:" << v.capacity() << std::endl; 23 24 v.clear(); 25 std::cout << "clear:" << v.capacity() << std::endl; 26 27 std::vector<char>().swap(v); 28 std::cout << "swap:" << v.capacity() << std::endl; 29 30 v.resize(10000); 31 std::cout << "resize:" << v.capacity() << std::endl; 32 33 while (!v.empty()) 34 { 35 v.erase(v.begin()); 36 } 37 v.shrink_to_fit(); 38 std::cout << "erase all & shrink_to_fit:" << v.capacity() << std::endl; 39 40 41 v.resize(10000); 42 std::cout << "resize:" << v.capacity() << std::endl; 43 44 v.clear(); 45 v.shrink_to_fit(); 46 std::cout << "clear & shrink_to_fit:" << v.capacity() << std::endl; 47 48 return 0; 49}
実行結果
Initial:0 resize:10000 erase all:10000 swap:0 resize:10000 clear:10000 swap:0 resize:10000 erase all & shrink_to_fit:0 resize:10000 clear & shrink_to_fit:0
投稿2020/07/15 07:33
編集2020/07/15 08:29総合スコア4079
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
Swap技法
は,
既存のvector Aと空のvector Bとでswapすることで,管理しているメモリ領域を交換する→Bが解体される際に(元々Aが管理していた)メモリが解放される
という話.
eraseをfor文で回すのと変わらなくないか
eraseだとcapacityが減らないという点では,clearと変わらないです.
投稿2020/07/15 07:25
総合スコア11954
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
vector型でClearを呼ぶ際にメモリが正常に開放されない
要素が削除されてもcapacityが小さくならないってーおハナシかしら。
C++11以降、shrink_to_fit() ってメンバ関数が追加されてます。
C++
1#include <iostream> 2#include <vector> 3 4int main() { 5 std::vector<int> v(10000); 6 std::cout << "capacity before clear : " << v.capacity() << std::endl; 7 v.clear(); // 削除してもキャパは変わらず 8 std::cout << "capacity after clear : " << v.capacity() << std::endl; 9 10#if 1 11 { 12 std::vector<int> empty; // 空のvectorを用意して 13 v.swap(empty); // 入れ替え 14 } 15 std::cout << "capacity after swap : " << v.capacity() << std::endl; 16#else 17 v.shrink_to_fit(); 18 std::cout << "capacity after shrink_to_fit : " << v.capacity() << std::endl; 19#endif 20 21}
投稿2020/07/15 06:46
総合スコア16612
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/07/15 08:53
2020/07/17 15:38
2020/07/17 15:41
2020/07/18 09:01
2020/07/20 08:55
2020/07/20 14:13
2020/07/20 16:30