ストラウストラップのプログラミング入門(27) 18章「ベクタと配列」
『ストラウストラップのプログラミング入門』を読む。今日は、18章「ベクタと配列」の18.3まで。
前章のドリルで悩んでいたvectorのコピーについては、この章で本格的に扱われていました。
ところで、ソ ラ ノートが他人事とは思えない今日この頃。あれほどのコンテンツ性がこの日記にはない上に、本に載ってるコードも引用しまくりんぐです。大丈夫かしらん。。。まぁ、引用元はくどいくらい明らかにしていきますです。
読書メモ
標準のvectorのコピーはディープコピーである
つまり、前回の読書日記の追記に書いたコードで、まるでシャローコピーを行うクラスに対するかのような心配をして、値を1個ずつせっせとコピーしようとしていましたが、
int main(){ vector<int> vv; // drill(13_10) for(int i = 0; i < 10; ++i){ if(i == 0){ vv.push_back(1); }else{ vv.push_back(vv[i - 1] * 2); } } vector<int>* vv2 = new vector<int>(10); for(int i = 0; i < (*vv2).size(); ++i){ // ★' (*vv2)[i] = vv[i]; } }
★'の箇所は、次のコードで十分なのですね。
vv2 = &vv; // ★
コピーコンストラクタとコピー代入
18.3.2「コンストラクタとデストラクタのデバッグ」では、コンストラクタとデストラクタの動きを確認し、理解するためのサンプルコードが提示されます。そこで、このコードを写経し、実行前に出力を予想した上で、実行結果と見比べました。すると案の定、理解不十分な点が浮き彫りになり、いいカンジに厭な感じになりました(……)。
次のコード(p.572)で、
struct X { int val; void out(const string& s, int nv) { cerr << this << "->" << s << ": " << val << " (" << nv << ")\n"; } X(){ out("X()", 0); val = 0; } X(int v) { out("X(int)", v); val = v; } X(const X& x) { out("X(X&) ", x.val); val = x.val; } X& operator=(const X& a) { out("X::operator=()", a.val); val = a.val; return *this; } ~X() { out("~X()", 0); } }; X copy(X a) { return a; } int main(){ X loc(4); X loc2 = loc; //(以下略) }
loc2を宣言してlocを代入している行では、コピーコンストラクタが実行されます。「=」という演算子に惑わされて、operator=()が呼ばれるのだとばかり思っていました。
左辺でクラスの宣言が行われたときは、コピー代入ではなく、コピーコンストラクタが呼ばれるのですね。
また、コピー代入の右辺で名前を付けずに作成したオブジェクトは、代入が済んだら破棄されることも理解しました。
コピー代入の実行箇所が謎
上記のmain関数は、こんな調子でどんどん続きます。
int main(){ X loc(4); X loc2 = loc; loc = X(5); loc2 = copy(loc); //(以下略) }
copy関数を使っている行で、コピー代入が2回呼ばれています。ナニコレ。いつの間に…。operator=()が呼ばれる前に、copy(loc)のどこかで呼ばれているのは間違いないのですが・・・よくワカラン。
無理やり考えてみると、
- copy関数の呼び出しの引数として、loc変数を評価した段階で、まず1回コピー代入が実行される
- 「copy(loc)」全体の評価が完了する段階で、もう1回コピー代入が実行される
- copy(loc)が分かれば、もう単体locはいらないので、1回目のデストラクタが走り、locのためのメモリが解放される
- operator=()が実行される
- copy(loc)自体も不要になるので、2回目のデストラクタが走り、copy(loc)の評価結果(?)が占めていたメモリが解放される
という感じでしょうか。サテハテ。。。