2012年6月15日金曜日

コンテナへのクラスの入れ方

使用してる環境は下記の通り

  • Windows XP SP3 32bit
  • Visual Studio 2008 SP1
  • boost 1.49.0

コンテナにクラスを入れること


オブジェクト指向言語の賜物であるクラスと、ジェネリックプログラミングの賜であるコンテナを組み合わせたら向かうところ敵なし!なのだけれど、C++において単純にコンテナにクラスをぶち込むと思わぬ副作用がある

仮にこんなクラスがあるとする

class A
{
public:
 A(int val = 0) : _val(val)
 {
  cout << "A constructor:" << _val << endl;
 }
 A(const A& a)
 {
  _val = a._val;
  cout << "A copy constructor:" << _val << endl;
 }
 const A& operator=(const A& a)
 {
  _val = a._val;
  cout << "A operator =:" << _val << endl;
  return a;
 }
 virtual ~A()
 {
  cout << "A destructor:" << _val << endl;
 }
 void print()
 {
  cout << "A:" << _val << endl;
 }
private:
 int _val;
};

それをこんな感じに、直接コンテナに入れて呼び出すとどうなるか?

void direct()
{
 cout << "direct" << endl;
 vector<A> v;

 v.push_back(A(1));
 v.push_back(A(2));
 v.push_back(A(3));

 BOOST_FOREACH(A &a, v)
 {
  a.print();
 }
}

direct()はmain関数から呼ばれてるだけの関数
すると、こんな感じになる


direct
A constructor:1
A copy constructor:1
A destructor:1
A constructor:2
A copy constructor:1
A copy constructor:2
A destructor:1
A destructor:2
A constructor:3
A copy constructor:1
A copy constructor:2
A copy constructor:3
A destructor:1
A destructor:2
A destructor:3
A:1
A:2
A:3
A destructor:1
A destructor:2
A destructor:3


コピコン(コピーコンストラクタ)呼ばれすぎぃ!
最初にコンテナに収める際にコピコンが使用されるのは仕方がないとして、コンテナに追加するごとに追加し終えた要素のコピコンまで呼ばれていたとは・・・たまげたなぁ

内部で何をやってるかは知らないけれど、この調子だとコンテナをアルゴリズムで使用したりするたびにコピコンが呼ばれてしまう
クラスが小さい場合はいいけれど、クラスが大きなヒープを保つ場合はメモリの再割当てを何度も行うハメになる
よろしくない


クラスのポインタをコンテナに入れる


ならばポインタだ

void normalPointer()
{
 cout << "normal Pointer" << endl;
 vector<A *> v;
 
 v.push_back(new A(4));
 v.push_back(new A(5));
 v.push_back(new A(6));

 BOOST_FOREACH(A *a, v)
 {
  a->print();
 }
}

するとこんな感じに


normal Pointer
A constructor:4
A constructor:5
A constructor:6
A:4
A:5
A:6


これでいいんじゃないの?というわけにもいかない
デストラクタがない
コンテナは直接納めてあるものに対しては自身が解放される際にデストラクタを呼んでくれるけれど、内部のポインタに対してはデストラクタを呼んでくれないらしい

じゃあ、コンテナとクラスを組み合わせて正しく使うにはどうすればいいのか

boostのshared pointerをコンテナに入れる


boostのshared pointerは自分を参照する相手が一人もいなくなった段階でデストラクタを呼ぶというすぐれもの

#include "boost/shared_ptr.hpp";

void sharedPointer()
{
 cout << "shared Pointer" << endl;

 typedef boost::shared_ptr<A> A_sptr;
 vector<A_sptr > v;

 v.push_back(A_sptr(new A(7)));
 v.push_back(A_sptr(new A(8)));
 v.push_back(A_sptr(new A(9)));

 BOOST_FOREACH(A_sptr &a, v)
 {
  //A *a_ptr = a.get();
  //a_ptr->print();
  a->print();
 }
}

ヘッダーはshared_ptr.hpp
毎回boost::shared_ptrを書くのが面倒なので、typedefしている
foreachで受け取れるのももちろんshared_ptrなのだけれど、これを受けてからget()を使用してポインタ変数を手に入れるのもいいけれど、途中で参照にすることで直接ポインタが手に入ることに気づいた
なるほど便利



shared Pointer
A constructor:7
A constructor:8
A constructor:9
A:7
A:8
A:9
A destructor:7
A destructor:8
A destructor:9


ポインタを入れた際と異なり、無事デストラクタが呼ばれている
これにて一件落着、かな?

0 件のコメント:

コメントを投稿