以前、Copy&Swap技法 について記事を書きましたが、今回はSwap技法を使った別のちょっとした小技を紹介しましょう。
オブジェクトを初期状態に戻すときに次のようにswapを使うことができるのです(ただしクラスのメンバ関数にswap関数が定義されている場合です)。
Widget widget;
widget.doSomething();
←オブジェクトの状態を変化させるメソッドの実行
...
Widget().swap(widget);
← オブジェクトを初期状態に戻すイディオム
...
Widget().swap(widget)
が何を行っていると言うと、まずWidget()
で名前のない一時オブジェクトをデフォルトコンストラクタで生成しています。そして生成された一時オブジェクトのswapメンバ関数を呼んで引数にwidgetを与えてます。そうすることで生成された一時オブジェクトとwidgetの中身が交換されるのです。widgetがなにかのリソースを確保している場合は、一時オブジェクトがそのリソースを引き取ってswap終了後に一時オブジェクトが破棄されるときにいっしょにリソースを解放してくれます。
もうちょっと具体的な例で見てみましょうか…
#include <vector>
#include <iostream>
using namespace std;
int main()
{
vector<int> v;
for (int i = 0; i < 10; ++i) {
v.push_back(i);
← vectorに要素を追加する
cout << "AFTER push_back:" << endl;
cout << "SIZE: " << v.size() << endl;
cout << "CAPA: " << v.capacity() << endl;
}
v.clear();
← 要素を空にする(通常はこれでよい)
cout << "AFTER clear:" << endl;
cout << "SIZE: " << v.size() << endl;
cout << "CAPA: " << v.capacity() << endl;
vector<int>().swap(v);
← 完全に初期状態に戻す(普通は使わない)
cout << "AFTER swap:" << endl;
cout << "SIZE: " << v.size() << endl;
cout << "CAPA: " << v.capacity() << endl;
return 0;
}
上のサンプルコードではvectorに要素を追加したり、clearしたり、swapしたりした後のsizeとcapacityを出力しています。vectorクラスのsizeは要素数を返しますが、capacityは要素を格納するために実際に確保している領域のサイズを返します。push_backで要素を追加するとsizeは1, 2, 3, 4, ... と順に増加しますが、capacityの方は1, 2, 4, 8, ... と倍倍で増加します(もちろん増加するのはsizeがcapacityに達したときだけです)。そして、clear実行後はsizeはもちろん0を返しますが、capacityの方は変化しません。これは、次にまた要素の追加があったときに効率的に要素を格納できるようにするために確保した領域をそのまま残しているからです(つまりメモリの再確保を頻繁に発生させないようにしているのです)。通常は要素を空にするのにclearを呼び出しますし、そうすべきなのですが、特別な理由でcapacityも空にしたい(領域も解放したい)という場合は、上の例のようにSwap技法を使います(これ以外にcapacityを空にする方法はないのです)。
めったに使わないテクニックかもしれませんが、まあ、知っておいて損はないでしょう。
追記:
このイディオムは Clear-and-minimize と呼ばれるそうです。また、これとよくにたイディオムに Shrink-to-fit というものがありますが、こちらは余分に確保しているメモリを解放する(v.size() == v.capacity()にする)ためのものです。
関連記事: