目下手を入れているコードも相当古く、ビギナさんにオブジェクト指向を教えるべく継承と多態を"わざと"多用したサンプルです。C++での動的束縛(dynamic-binding)を説明するため、あちこちにポインタが現れあちこちでnew/deleteしています。多態を使いまくるコードでポインタの利用が増えてしまうのはある程度仕方のないことなのですが、決してnew/deleteしまくりたいわけじゃありません。ガベージ・コレクションをサポートしたC#/Javaの類ならnewしっぱなしで構わんけれど、C++ではnewしたものは必ず/明示的に/ただ一度だけ/確実にdeleteしてやらにゃなりません。ヒトは間違う生きもの、忘れる生きものなんだから、できることならnew/deleteしたくない。特にdeleteはやらずに済むならそれに越したことはないわけで...マクラが長くなりました、unique_ptrのおはなしです。
かなり賢いunique_ptr
C++11で、今まであった(今もあるけど)auto_ptrに取って代わるunique_ptr、要はデストラクタで自動的にdeleteしてくれる"賢いポインタ(smart-pointer)"の一つです。unique_ptrのデストラクタが内包するポインタの解放を行うのでnewしっぱなしでも大丈夫。ちょっとコード書いてみますね。
#include <iostream> #include <sstream> #include <string> #include <memory> using namespace std; class foo { public: foo(const char* name ="foo", int value =0) : name_(name), value_(value) { cout << symbol() << " [BORN]\n"; } ~foo() { cout << symbol() << " [DEAD]\n"; } string symbol() const { ostringstream stream; stream << name() << '(' << value() << ')'; return stream.str(); } string name() const { return name_; } int value() const { return value_; } private: string name_; int value_; }; string symbol(const unique_ptr<foo>& upfoo) { return upfoo ? upfoo->symbol() : "(null)"; } int main() { unique_ptr<foo> p(new foo("adam",20)); { unique_ptr<foo> p(new foo("eve",19)); cout << symbol(p) << " alive!" << endl; // ここで eve が死ぬ } cout << symbol(p) << " alive!" << endl; // ここで adam が死ぬ } /* 実行結果 adam(20) [BORN] eve(19) [BORN] eve(19) alive! eve(19) [DEAD] adam(20) alive! adam(20) [DEAD] */
unique_ptrはその名のとおりユニークすなわち"たった1つのポインタ"であり、ポインタの所有権と解放義務を独占します。2つのunique_ptr: poldとpnewがあって、poldからpnewへのポインタの移動(move)を行うと、pnewがつかんでいたポインタが解放され、/poldからpnewへポインタがコピーされて/poldはnullptrになります。unique_ptrがポインタを手放すとき(デストラクト時も)確実にdeleteしてくれるのでポインタの解放もれが起こりません。例外がthrowされる場合でもデストラクタが動くので大丈夫。
unique_ptrと同等の機能を持ったauto_ptrはC++11ではdeprecated(廃止予定/非推奨)となりました。auto_ptrはうっかりミスを起こしがちなんです。例えば:
// foo はそのまま... string symbol(const auto_ptr<foo>& apfoo) { return apfoo.get() ? apfoo->symbol() : "(null)"; } int main() { auto_ptr<foo> pold(new foo("adam",20)); auto_ptr<foo> pnew(new foo("eve" ,19)); cout << "before : " << symbol(pold) << ' ' << symbol(pnew) << endl; pnew = pold; cout << "after : " << symbol(pold) << ' ' << symbol(pnew) << endl; } /* 実行結果 adam(20) [BORN] eve(19) [BORN] before : adam(20) eve(19) eve(19) [DEAD] after : (null) adam(20) adam(20) [DEAD] */
pnew = pold;でpoldからpnewへポインタが"移動"し、poldはnullptrです。=はコピー演算子と呼ばれているとおり、右辺を左辺にコピーするもののはず。ところがauto_ptrに関しては見た目はcopyなのに挙動はmoveなのです。あるいは例えば:
… int main() { auto print = [](auto_ptr<foo> apfoo) { cout << symbol(apfoo) << endl; }; auto_ptr<foo> p(new foo("adam",20)); print(p); print(p); } /* 実行結果 adam(20) [BORN] adam(20) adam(20) [DEAD] (null) */
のように、関数の引数に渡しただけで引数へのcopy...実はmoveされちゃって、呼出し後のpが失われます。
uniqur_ptrは"実はmoveを引き起こすcopy"ができないように、
unique_ptr(const unique_ptr&) =delete; unique_ptr& operator=(const unique_ptr&) =delete;
となっており、そのかわりに右辺値参照を引数とする
unique_ptr(unique_ptr&&); unique_ptr& operator=(unique_ptr&&);
が定義されています。左辺値からはcopyできないので、unique_ptr間の移動は明示的にstd::move()しなければなりません。
unique_ptr<foo> pold(new foo("adam",20)); unique_ptr<foo> pnew(new foo("eve" ,19)); cout << "before : " << symbol(pold) << ' ' << symbol(pnew) << endl; pnew = move(pold); //pnew = pold; // error! : =でコピーできない cout << "after : " << symbol(pold) << ' ' << symbol(pnew) << endl;
想定外の事態を起こさないように作られていますね。右辺値参照が効果的に使われた一例といえましょう。