回答編集履歴

1

tuiki

2020/07/16 09:17

投稿

yumetodo
yumetodo

スコア5852

test CHANGED
@@ -3,3 +3,153 @@
3
3
 
4
4
 
5
5
  古い本のベストプラクティスとして紹介されている技法はとくにC++11の登場によって不要になるかバッドプラクティスとなっているものが多いので、適用してみようとする前にその技法が今どういう評価を受けているかは慎重に調べることをおすすめします。
6
+
7
+
8
+
9
+ ---
10
+
11
+
12
+
13
+ 追記
14
+
15
+
16
+
17
+ > ```cpp
18
+
19
+ class A
20
+
21
+ A *a = new A();
22
+
23
+ vector<A> avec;
24
+
25
+ a.push_buck(a);
26
+
27
+ > ```
28
+
29
+ >
30
+
31
+ > このavecを開放しようとなると
32
+
33
+ >
34
+
35
+ > ```cpp
36
+
37
+ delete a;
38
+
39
+ shrink_to_fit();
40
+
41
+ > ```
42
+
43
+ >
44
+
45
+ > と呼ばないと全メモリは開放されない感じなんですかね?
46
+
47
+ > aで確保したアドレスとavecに挿入後のアドレスは変らないようですが
48
+
49
+
50
+
51
+ 待ってください、そのコードは**だいたい全部おかしいです**。
52
+
53
+
54
+
55
+ ## 型の問題
56
+
57
+
58
+
59
+ 変数`avec`の型は`std::vector<A>`です。ですから要素の型は`A`です。`A*`ではありません。したがって`a.push_buck(a);` はコンパイルエラーになります。
60
+
61
+
62
+
63
+ ## `std::vector`のデータ構造について
64
+
65
+
66
+
67
+ swap技法にしろ`shrink_to_fit`にしろ、何を目的としたものなのか把握されていないように思います。**間違っても全メモリーを開放することを目的としたものではありません。**
68
+
69
+ `std::vector`はどのようにメモリーを使うか理解していないのではありませんか?
70
+
71
+
72
+
73
+ `std::vector`を単純化すると次のような構造体になります(現実にはアロケータがここに加わりますが割愛します)。
74
+
75
+
76
+
77
+ ```cpp
78
+
79
+ template<typename T>
80
+
81
+ struct vec {
82
+
83
+ T* p;
84
+
85
+ std::size_t size;
86
+
87
+ std::size_t capacity;
88
+
89
+ };
90
+
91
+ ```
92
+
93
+
94
+
95
+ `capacity`は確保されているメモリー領域の大きさ、`size`は格納されている要素数です。つねに`size <= capacity`の関係になります。
96
+
97
+ 例えば`std::vector::push_back`のようなメンバ関数を呼び出すとき、もし`size == capacity`の状態にあると、新たな要素を追加することはできません。そこでメモリーを新規に確保して(一般に`capacity`の1.5~2倍)、すでにある要素をそちらに移して(厳密にはmove操作が無例外で行えるならmove, さもなくばcopy)、今まで割り当てていたメモリーを開放し、`capacity`を更新して、末尾に新規要素を追加し、`size`を更新します。
98
+
99
+
100
+
101
+ vectorに様々な操作をするにつれて、時として`capacity`が`size`よりも遥かに大きい状態になることがあります。例えば要素の大量削除をしたときなどです。このとき、実行速度を犠牲にしてでも(一般にメモリーの確保作業にはとても時間がかかります)メモリー使用量を削減したいという需要が発生します。この需要を満たすための手段がかつてのswap技法や、`std::vector::shrink_to_fit`の呼び出しです。
102
+
103
+
104
+
105
+ ここでお示しのコードを上述の型の問題を解決するように書き直してみましょう。
106
+
107
+
108
+
109
+ ```cpp
110
+
111
+ class A;
112
+
113
+
114
+
115
+ void foo()
116
+
117
+ {
118
+
119
+ A* a = new A();
120
+
121
+ std:vector<A*> v;
122
+
123
+ v.push_back(a);
124
+
125
+ delete a;
126
+
127
+ a.shrink_to_fit();
128
+
129
+ }
130
+
131
+ ```
132
+
133
+
134
+
135
+ さて、変数`a`は`A*`型の変数で、new演算子によって割り当てられたメモリーを指し示すポインタです。
136
+
137
+ これを変数`v`のvectorに追加します。つまり`v.push_back(a);`の直後ではnew演算子によって割り当てられたメモリーを指し示しているポインタは、`a`と`v[0]`の2つ存在しています。
138
+
139
+ `delete a;`については一度飛ばします。
140
+
141
+
142
+
143
+ では、`a.shrink_to_fit();`の意味をここで考えましょう。変数`v`、つまりvectorには要素が追加されるだけで削除はしていません。このとき`capacity`が`size`よりも遥かに大きい状態になることは起こりえません。すると`a.shrink_to_fit();`は無意味ということが言えます。
144
+
145
+
146
+
147
+ SHOMIさんやepistemeさんの回答にあるサンプルコードをよく見てください。
148
+
149
+
150
+
151
+ ## dangling pointer
152
+
153
+
154
+
155
+ `delete a`について考えます。ここでnew演算子によって割り当てられたメモリーが開放されます。つまり`a`と`v[0]`は存在しない領域へのポインタになったわけです。存在しない領域へのポインタのことを一般にdangling pointerと呼びます。例えばこのあとに`*v[0]`とかやるとdangling pointerをdereferenceしていますから未定義動作となり[コンパイラはタイムトラベル](https://cpplover.blogspot.com/2014/06/old-new-thing.html)したり鼻から悪魔を召喚したりする可能性がありえます。