質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
STL

STL(Standard Template Library)は、ジェネティックコンテイナー、イテレーター、アルゴリズム、そして関数オブジェクトのC++ライブラリーです。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

4回答

13113閲覧

Swap技法について

apa

総合スコア68

STL

STL(Standard Template Library)は、ジェネティックコンテイナー、イテレーター、アルゴリズム、そして関数オブジェクトのC++ライブラリーです。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

0クリップ

投稿2020/07/15 06:23

リーダブルコードという本でSwap技法なるものを知りました。
調べてみるとvector型でClearを呼ぶ際にメモリが正常に開放されないからSwapを使うというものらしいです。
ただそれってvectorにあるeraseをfor文で回すのと変わらなくないかと思っています。
swapによるメモリ解放とeraseによるメモリ開放違いってなんなんでしょうか?

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答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に様々な操作をするにつれて、時としてcapacitysizeよりも遥かに大きい状態になることがあります。例えば要素の大量削除をしたときなどです。このとき、実行速度を犠牲にしてでも(一般にメモリーの確保作業にはとても時間がかかります)メモリー使用量を削減したいという需要が発生します。この需要を満たすための手段がかつての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}

さて、変数aA*型の変数で、new演算子によって割り当てられたメモリーを指し示すポインタです。
これを変数vのvectorに追加します。つまりv.push_back(a);の直後ではnew演算子によって割り当てられたメモリーを指し示しているポインタは、av[0]の2つ存在しています。
delete a;については一度飛ばします。

では、a.shrink_to_fit();の意味をここで考えましょう。変数v、つまりvectorには要素が追加されるだけで削除はしていません。このときcapacitysizeよりも遥かに大きい状態になることは起こりえません。するとa.shrink_to_fit();は無意味ということが言えます。

SHOMIさんやepistemeさんの回答にあるサンプルコードをよく見てください。

## dangling pointer

delete aについて考えます。ここでnew演算子によって割り当てられたメモリーが開放されます。つまりav[0]は存在しない領域へのポインタになったわけです。存在しない領域へのポインタのことを一般にdangling pointerと呼びます。例えばこのあとに*v[0]とかやるとdangling pointerをdereferenceしていますから未定義動作となりコンパイラはタイムトラベルしたり鼻から悪魔を召喚したりする可能性がありえます。

投稿2020/07/15 08:22

編集2020/07/16 09:17
yumetodo

総合スコア5852

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

apa

2020/07/15 08:53

回答ありがとうございます。 つまり class A A *a = new A(); vector<A> avec; a.push_buck(a); このavecを開放しようとなると delete a; shrink_to_fit();と呼ばないと全メモリは開放されない感じなんですかね? aで確保したアドレスとavecに挿入後のアドレスは変らないようですが
apa

2020/07/17 15:38

返信おくれました  あとvector<A>の*抜けのところ完全にうちミスです。混乱させてしまいすいません。   なるほどだいぶわかった気がします。 ただそもそもなにもさしていない(deleteで空になっている)vector型の avecっていくらcapacityがあろうが空である以上メモリに何ら影響がないように思えてしまいます。 そうではないなにか特殊な規約があるのでしょうか?
apa

2020/07/17 15:41

ただこの質問はvector型に対してになってしまうので もう少しvectorについて調べてきます。
apa

2020/07/18 09:01

capacityについて調べてきました。 ゲームループなどでメモリが増えつつづけることはないですが、 メモリ領域の圧迫につながるため shrink_to_fit()を呼ぶことを推奨されているという 認識であっていますでしょうか?
yumetodo

2020/07/20 08:55

>shrink_to_fit()を呼ぶことを推奨されている いえ、実行速度を犠牲にしてでも(一般にメモリーの確保作業にはとても時間がかかります)メモリー使用量を削減したいという需要が発生したときに呼び出します。
apa

2020/07/20 14:13

メモリが足りているようなら大丈夫みたいですね! なるほどわかりやすい説明ありがとうございました!! また一歩成長できました
yumetodo

2020/07/20 16:30

いい忘れてましたが > (deleteで空になっている)vector型の そのdeleteはvectorのsize/capacityになんら関係のないものです。全くの無関係です。念の為。
guest

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
SHOMI

総合スコア4079

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

Swap技法

は,
既存のvector Aと空のvector Bとでswapすることで,管理しているメモリ領域を交換する→Bが解体される際に(元々Aが管理していた)メモリが解放される
という話.

eraseをfor文で回すのと変わらなくないか

eraseだとcapacityが減らないという点では,clearと変わらないです.

投稿2020/07/15 07:25

fana

総合スコア11996

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

fana

2020/07/15 09:43

他の回答にて述べられているように,Swap技法はもはや使うことはないでしょうが… 複数の人間がたずさわる場面でこのような技法を使う際には,コードの該当箇所にコメントを書きましょう. //Swap技法 みたく. それを怠ると,その技法を知らない人にコードを意図しない形に修正されたり(例えばSwap技法ならば,clear()に書きなおされるとか),さらには「あいつは意味不明なクソコードを書く」とかいう悪評まで流されたりもします.
guest

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

episteme

総合スコア16612

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問