SHOEISHA iD

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

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

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

BoostでC++0xのライブラリ「TR1」を先取りしよう

BoostでC++0xのライブラリ「TR1」を先取りしよう (1)

arrayとshared_ptr/weak_ptr


悩ましきメモリ管理 - shared_ptr / weak_ptr

 C/C++でのメモリ管理はプログラマに委ねられます。厄介なことにプログラマのちょっとしたミスが大事故に繋がるのが悩みのタネ。

ポインタは厄介だ…
string* p = new string("one");
string* q = new string("two");
p = q; // p,q 共に "two" を指す。 "one"は迷子に。
delete p;
delete q; // "two"を多重delete!

std::auto_ptrの限界

 標準C++ライブラリはメモリ管理を楽にするstd::auto_ptrを提供しています。

auto_ptrだと
#include <iostream>
#include <string>
#include <memory> // std::auto_ptr<T>

using namespace std;

class item {
private:
  string value_;
public:
  item(const char* v="???") : value_(v) {
    cout << "item(" << value_ << ") ctor\n"; }
 ~item() { cout << "item(" << value_ << ") dtor\n"; }
  string value() const { return value_; }
};

int main() {
  auto_ptr<item> p(new item("one"));
  auto_ptr<item> q(new item("two"));
  p = q; // pは指していた"one"をdeleteし、"two"を指す。qは空(null)となる。
  cout << "p points to " << (p.get() ? p->value() : "(null)") << endl;
  cout << "q points to " << (q.get() ? q->value() : "(null)") << endl;
}
実行結果
item(one) ctor
item(two) ctor
item(one) dtor
p points to two
q points to (null)
item(two) dtor

 実行結果が示すとおり、コンストラクタとデストラクタの数が一致していますからdelete忘れ多重deleteが発生していないことが分かります。auto_ptrはデストラクト時にそれが指すポインタをdeleteするので明示的にdeleteする必要がありません。

 ただし、auto_ptr間のコピー(代入)が行われるとポインタの所有権(=削除責任)が移動し、コピー元からはポインタが失われます(実行結果のqが"two"を指していませんね)。そのため複数のauto_ptrが一つのインスタンスを指して共有することができません。

auto_ptrではインスタンスを共有できない
class person {
  string name_;
public:
  person(string n) : namae_(n) {}
  auto_ptr<person> child; // 子供
};

person sazae("サザエ");
person masuo("マスオ");
auto_ptr<person> tara(new person("タラ"));
// "タラ"を"サザエ"と"マスオ"の子としたいが
sazae.child = tara; // この瞬間"タラ"の所有権がtaraから
                    // sazae.childに移動する(譲渡される)ため
masuo.child = tara; // masuo.childは"タラ"を指せない

 newされたインスタンスを自動的にdeleteしてくれるauto_ptrは便利には違いないのですが、この制限があるために用途が限られてしまうのです。

std::tr1::shared_ptr - 共有ポインタ

 TR1で新たに追加されるshared_ptrは参照カウントというからくりでインスタンスを管理します。shared_ptrはその内部にそのインスタンスを参照しているshared_ptrの総数を保持しています。shared_ptrのデストラクタは内部の参照数を-1し、それが0となったときインスタンスがどこからも参照されなくなったと判断してインスタンスをdeleteします。

shared_ptr
#include <iostream>
#include <string>
#include <boost/tr1/memory.hpp> // std::tr1::shared_ptr<T>

using namespace std;

class item {
private:
  string value_;
public:
  item(const char* v="???") : value_(v) {
    cout << "item(" << value_ << ") ctor\n"; }
 ~item() { cout << "item(" << value_ << ") dtor\n"; }
  string value() const { return value_; }
};

int main() {
  // p1は"something"を指す。参照数:1
  tr1::shared_ptr<item> p1(new item("something"));
  cout << "p1->value() = " << p1->value() << endl;
  {
    // p2も"something"を指す。参照数:2
    tr1::shared_ptr<item> p2 = p1;
    cout << "p2->value() = " << p2->value() << endl;
    // ここでp2が消える。参照回数:1
  }
  cout << "p1->value() = " << p1->value() << endl;
  // ここでp1が消える。参照数:0となり、"something"がdeleteされる
}
実行結果
item(something) ctor
p1->value() = something
p2->value() = something
p1->value() = something
item(something) dtor

std::tr1:weak_ptr 弱参照ポインタ

 shared_ptrを使うことによって繁雑なメモリ管理から解放されるのですが、これでもまだ循環参照という問題が残っています。

循環参照とは
#include <iostream>
#include <string>
#include <boost/tr1/memory.hpp> // std::tr1::shared_ptr<T>

class Person {
public:
  string name; // 名前
  tr1::shared_ptr<Person> spouse; // 配偶者
  Person(string n) : name(n) {}
  void info() const {
    cout << "My name is " << name
         << " and my spouse is " << spouse->name << endl;
  }
};

int main() {
  // oneは"adam"を指す。 参照数:1
  tr1::shared_ptr<Person> one(new Person("adam"));
  {
    // twoは"eve"を指す: 参照数:1
    tr1::shared_ptr<Person> two(new Person("eve"));
    one->spouse = two; // "adam"の妻は"eve" 参照数:2
    two->spouse = one; // "eve"の夫は"adam" 参照数:2
    one->info();
    two->info();
    // ここでtwoが消える。参照数:1 ... 0ではないので"eve"はdeleteされない
  }
  one->info();
  // ここoneが消える。参照数:1 ... 0ではないので"adam"はdeleteされない
}
実行結果
My name is adam and my spouse is eve
My name is eve and my spouse is adam
My name is adam and my spouse is eve

 この例のように、複数のshared_ptrが互いを参照しあってループを形成する(循環する)と参照数が0になることがないため、インスタンスがdeleteされずに残ってしまうのです。

 この問題を解消するため、TR1はさらにもう一つ、weak_ptrを提供します。weak_ptrshared_ptrが保持する参照数の増減に関与しません。さらにweak_ptrのメンバexpired()によってインスタンスの消失を知ることができます。

weak_ptrによる解決
#include <iostream>
#include <string>
#include <boost/tr1/memory.hpp>
  // std::tr1::shared_ptr<T>, std::tr1::weak_ptr<T>

class Person {
public:
  string name;
  tr1::weak_ptr<Person> spouse;
  Person(string n) : name(n) {}
  void info() const {
    cout << "My name is " << name
         << " and my spouse ";
    if ( spouse.expired() ) // インスタンスの有無を判断する
      cout << "has gone...\n";
    else
      cout << spouse.lock()->name << endl;
  }
};

int main() {
  // oneは"adam"を指す。 参照数:1
  tr1::shared_ptr<Person2> one(new Person2("adam"));
  {
    // twoは"eve"を指す: 参照数:1
    tr1::shared_ptr<Person2> two(new Person2("eve"));
    // weak_ptrは参照数の増減に関与しない
    one->spouse = two; // "adam"の妻は"eve" 参照数:1
    two->spouse = one; // "eve"の夫は"adam" 参照数:1
    one->info();
    two->info();
    // ここでtwoが消える。参照数:0となり"eve"はdeleteされる
  }
  one->info();
  // ここoneが消える。参照数:0となり"adam"はdeleteされる
}
実行結果
My name is adam and my spouse eve
My name is eve and my spouse adam
My name is adam and my spouse has gone...

まとめ

 次期C++規格「C++0x」で拡張予定のライブラリ「TR1」からarrayshared_ptr/weak_ptrを紹介しました。これらを活用することでC++につきものの厄介なメモリ管理がぐっと楽になることでしょう。

 TR1にはその他にも便利なクラスが多数収録されています。続編をお楽しみに。

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

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
BoostでC++0xのライブラリ「TR1」を先取りしよう連載記事一覧

もっと読む

この記事の著者

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

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

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

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

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/1937 2008/08/20 11:57

イベント

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

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

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

メールバックナンバー