SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

「never say new, never say delete」
~C++11のunique_ptrのおはなし

unique_ptr ひとめぐり

  • X ポスト
  • このエントリーをはてなブックマークに追加

  Windows 8.1にあわせ、Visual Studio 2013がリリースされました。早速愛機にインストール(RC版から差し替え)し、これまでに書き溜めたサンプルやライブラリ、実験コードのre-buildを始めています。re-buildがてら古いコードを見渡してイケてないコードをお化粧直しするチャンスでもありますし。その中には20世紀に書いたものもあったりします。あんまり使わんもんだからメンテをサボってて、今となっては時代遅れのコードも少なくありません。

  • X ポスト
  • このエントリーをはてなブックマークに追加

 目下手を入れているコードも相当古く、ビギナさんにオブジェクト指向を教えるべく継承と多態を"わざと"多用したサンプルです。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しっぱなしでも大丈夫。ちょっとコード書いてみますね。

list01
#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はうっかりミスを起こしがちなんです。例えば:

list02
// 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なのです。あるいは例えば:

list03
…
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()しなければなりません。

list04
  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;

 想定外の事態を起こさないように作られていますね。右辺値参照が効果的に使われた一例といえましょう。

会員登録無料すると、続きをお読みいただけます

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

次のページ
配列への対応とmake_unique

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
特集記事連載記事一覧

もっと読む

この記事の著者

επιστημη(エピステーメー)

C++に首まで浸かったプログラマ。Microsoft MVP, Visual C++ (2004.01~2018.06) "だった"りわんくま同盟でたまにセッションスピーカやったり中国茶淹れてにわか茶...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/7479 2013/11/14 14:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング