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


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

2012年6月14日木曜日

Visual Studio 2008 で boost

使用した環境は、

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

boostライブラリをダウンロード


http://www.boost.org/users/download/


100MB近くある、結構でかい
これを適当なところに解凍しておく
自分の場合は、

C:\Program Files\boost\boost_1_49_0

に配置した


Visual Studio 2008にて新規プロジェクト作成


Win32コンソールアプリケーションを選択する


プロジェクトの場所、名前はなんでも構わないはずだけれど、念のため日本語は控えたほうがいいかもしれない

Win32 アプリケーションウィザードはそのまま完了を選択してかまわない



インクルードパスの設定


プロジェクトのプロパティから


構成プロパティ→C/C++→全般→追加のインクルードディレクトリ


配置したboostのフォルダを選択
バージョン番号の書いてあるフォルダからぐらいでかまわない


boostライブラリのほとんどはヘッダーのみで動いてくれるものなので、ヘッダー設定さえあれば基本的なものは動作可能らしい
すごい


サンプルコードの動作を確認する


自分の試したサンプルコード

// boosttest2.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"

#include <iostream>
#include <list>
#include <algorithm>

#include "boost/foreach.hpp";

using namespace std;

void print(int i)
{
 cout << i << " ";
}

int _tmain(int argc, _TCHAR* argv[])
{
 list<int> ol;

 for(int i = 0; i < 10; ++i)
 {
  ol.push_back(i);
 }

 for_each(ol.begin(), ol.end(), print);
 cout << endl;

 BOOST_FOREACH(int i, ol)
 {
  cout << i << " ";
 }

 return 0;
}


何かとお世話になるforeach文をSTLとboostでそれぞれ実行してみている
関数(あるいは関数オブジェクト)を用意する手間がないぶん、boostのほうが素敵
ただし、大文字なのはちょっといただけないかな

これで問題なくコンパイル、実行が可能なはず!

テスト投稿

テスト投稿