Ownway.Info
 
HOMEC++における基本的なところ → スマートポインタについて
スマートポインタについて

1: スマートポインタについて

JAVA や C# などの新しい言語は、ガーベジコレクタと呼ばれるメモリ自動管理機構を言語の仕様として持っている。 それに対して C/C++ はメモリを管理するコードを手動で書かなければならない。 これを簡単にコードとして書くならばすなわち、

Class1* object=new Class1();

// ... 何らかの処理を行う

delete object;

以上のように、生成(new)したオブジェクトは必ず自分で破棄(delete)せよということである。 スマートポインタとは、この生成・破棄のパターンをカプセル化によって一般化し、 生成したオブジェクトを自動的に破棄する機構を提供するものである。 といってもこれはそんなに特別なものではなく、あくまでC/C++の言語の上にのった小さなプログラムに他ならない。 スマートポインタの正しい使い方を覚えるとプログラムがスマートに書けるようになる。 ここではスマートポインタを使った例をいくつか紹介する。

■ スマートポインタのセットアップ

まずはスマートポインタが使えるようにコンピュータをセットアップしなければならない。 スマートポインタは自分で作ることもできるが、Boost C++ Libraries というライブラリにその実装が含まれており、 本ページでは主にこのスマートポインタを用いる。 以下のサイトを訪れて Boost C++ Libraries をコンピュータにインストールしよう。

このページは英語で書かれているが、ページ左のDownloadをクリックして先に進んで行けば良い。 基本的にスマートポインタはテンプレートとして書かれているのでコードをコンパイルするという必要は無く、 インクルードディレクトリとしてライブラリのルートディレクトリにパスを通せばセットアップは完了である。

2: スマートポインタを使った一番簡単なプログラム

スマートポインタを使わないプログラムと、 使ったプログラムの比較を行おう。 まず最初に、スマートポインタを使わない一番簡単なプログラムを以下に示す。

#include <iostream>
#include <string>
using namespace std;

class Class1 {
private:
  const string name_;

public:
  Class1(const string& name) : name_(name) {
    cout << name_ << "を生成しました。" << endl;
  }

  virtual ~Class1() {
    cout << name_ << "を破棄しました。" << endl;
  }

public:
  void method() {
    cout << name_ << "のメソッドを呼び出しました。" << endl;
  }
};

int main() {
  Class1* a=new Class1("a");

  a->method();

  delete a;

  return 0;
}

Class1は、生成されたときに呼び出されるコンストラクタと破棄されたときに呼び出されるデストラクタで メッセージを出力する簡単なプログラムである。 また、Class1はメソッドを一つ持っていて、それを呼び出すことでもメッセージを出力する。 Class1は属性として名前を持っており、何という名前のオブジェクトがメッセージを出力しているのかがわかるようになっている。

このサンプルのメイン関数は実に簡単で、Class1のオブジェクトaを構築し、 methodを呼び出し、最後に破棄するだけのものである。 これはまさにオブジェクトの生成と破棄の最も簡単なサイクルそのものであり、 以上のプログラムをコンパイルして実行すると以下のような結果が得られる。

aを構築しました。
aのメソッドを呼び出しました。
aを破棄しました。

以上のプログラムをBoost C++ Librariesの boost::shared_ptrというスマートポインタを使って書くと以下のようになる。

#include <iostream>
#include <string>
using namespace std;

#include <boost/shared_ptr.hpp>

class Class1 {
private:
  const string name_;

public:
  Class1(const string& name) : name_(name) {
    cout << name_ << "を構築しました。" << endl;
  }

  virtual ~Class1() {
    cout << name_ << "を破棄しました。" << endl;
  }

public:
  void method() {
    cout << name_ << "のメソッドを呼び出しました。" << endl;
  }
};

// スマートポインタ型としてClass1Ptrを定義する
typedef boost::shared_ptr<Class1> Class1Ptr;

int main() {
  Class1Ptr a(new Class1("a"));

  a->method();

  return 0;
}

boost::shared_ptrを使うためには、まずboost/shared_ptr.hppをインクルードする。 次に、shared_ptrはテンプレートなのでClass1をテンプレートに当てはめ、 Class1専用のスマートポインタとしてClass1Ptrという型を定義する。

このサンプルのメイン関数は実に簡単で、 Class1のオブジェクトaを構築し、そのメソッドmethodを呼び出している。 前のサンプルと違いオブジェクトaを破棄する記述が無いが、このプログラムを実行すると 前のサンプルと同じ実行結果が得られる。 なぜそうなるかというと、Class1Ptrクラスがオブジェクトaの破棄を自動的にしてくれるからである。 具体的には、Class1Ptrクラスのデストラクタでオブジェクトaは破棄されている。 簡単に言うとClass1Ptr変数のスコープが、 生成したオブジェクトの生存区間を決定するようにプログラムを書くことができるようになったということだ。 また、Class1Ptrクラスはアロー演算子(->)を適切にオーバーライドしており、 普通のポインタのメソッドを呼び出すときと同じようにしてメソッドを呼び出すことができる。

ついでに以上のプログラムのメイン関数をちょっとだけ書き換えて、 a,bという二つのオブジェクトを生成するように以下のように書き換えて見よう。

int main() {
  Class1Ptr a(new Class1("a"));
  Class1Ptr b(new Class1("b"));

  a->method();
  b->method();

  return 0;
}

すると実行結果は以下のようになる。

aを構築しました。
bを構築しました。
aのメソッドを呼び出しました。
bのメソッドを呼び出しました。
bを破棄しました。
aを破棄しました。

ここで重要なのは変数を宣言した順番と、その破棄の順番がちょうど逆になるということである。 これでスマートポインタの一番簡単な使い方の説明を終える。

3: さまざまな局面におけるスマートポインタの使い方

スマートポインタの使い方に関してこれ以上詳しく説明はしないが、 さまざまな局面におけるスマートポインタの使い方を示すサンプルプログラムを以下にいくつか示す。 いずれもスマートポインタを使った一番簡単なプログラムを、 ある問題に焦点を当てて書き直した非常に簡単なものなので必要に応じてちょっと見て動かして見ると 何かわかるかもしれない。

■ スマートポインタに配列のポインタを渡す場合 [use] [nouse]

オブジェクトを破棄するdeleteは、単体のオブジェクトの場合と配列の場合とでは異なる。 そのため配列のポインタを扱う場合にはboost::shared_ptrの代わりにboost::shared_arrayを用いなければならない。

■ スマートポインタを関数に渡す場合 [use] [nouse]

スマートポインタも普通のポインタと同じように、もちろん関数に渡すことができる。 これはそれだけの簡単な例である。

■ スマートポインタに新しいポインタを代入する場合 [use] [nouse]

ポインタ変数が、途中で別のポインタを指すようになるという例。 この場合、ポインタ変数は新しいオブジェクトのポインタを代入する前に 必要ならば古いオブジェクトを事前に破棄しなければならない。 スマートポインタは代入時に自動的に古いオブジェクトを破棄してくれる。 必要ならばとは、そのオブジェクトを参照しているポインタ変数が他にもある場合は、 そのオブジェクトを破棄してはならないからである。 boost::shared_ptrというスマートポインタは、参照カウンタ型スマートポインタと呼ばれるもので、 自分が保持するポインタがどれだけ使われているかという数を自動的に数えており、 それに従ってポインタを破棄するべきかどうかを判断している。 すなわち参照カウンタが0になったら、そのオブジェクトは破棄して良いということだ。

■ 複数のオブジェクトが一つのポインタを共有する場合 [use] [nouse]

上のサンプルでは、オブジェクトが常にただ一つのスマートポインタによってしか参照されておらず、 参照カウンタの効果がいまいち発揮されていないが、これはそれを発揮している簡単な例である。

■ スマートポインタでポリモフィズム [use] [nouse]

スマートポインタも普通のポインタと同じように、もちろんポリモフィズムを実現することができる。 これはその簡単な例である。

■ 関数の出口が複数ある場合(スマートポインタを使うと楽だと思える例) [use] [nouse]

生成と破棄は対で現れるので、関数の入口と出口でこれを制御すれば良いという考え方がある。 ただし、関数の入口は常に一つだが出口は常に一つとは限らない。 そんな場合、全ての出口で適切な破棄の記述を書かなくてはいけない。 スマートポインタを使うと、そのわずらわしさから解放されてスマートな記述ができるという例である。

■ スマートポインタがうまく働かない例 [use]

スマートポインタも万能ではない。スマートポインタがうまく働かずオブジェクトが正常に破棄されない場合がある。 それはポインタの参照がサイクリック(aがbに、bがcに、cがaにとグルグル周ってしまう関係)になってしまったときである。 なぜならお互いの参照カウンタが常に0にならないからだ。これはその簡単な例である。 スマートポインタは簡単で非常に効果的だが万能ではないことを知っておこう。

4: 参考文献
■ More Effective C++ 最新35のプログラミング技法

スマートポインターの存在に出会った最初の本である。 スマートポインターのみならず C++ における数多くの慣習や技能を身につけることができる。 2010/10/22 辞典での Amazon のカスタマーレビューを見たところによると日本語訳が酷評されているが、 ある程度の能力を事前に持っていればサンプルとして掲示されているソースコードから、逆に文章の誤りを正して読み直すことができるだろうと思う。 本書は、既に古く新改訂版が出ているようなのでそちらの購入をお勧めする。 残念ながら新改訂版を読んではいないが、スマートポインターの記載がなくなっているとは思えない。 欲しいと思う人は一度手にとって見てから買うと良いだろう。

More Effective C++ は、前作に Effective C++ という本があり、その続編である。 したがって、Effective C++ も参考にすることでスマートポインターのみならず多くの知見を得ることができるだろう。

こちらも新改訂版が出ているようなので、これから購入を検討する場合はそちらの購入をお勧めする。

■ Boost C++ Library プログラミング

本ページで紹介している Boost C++ Libraries の解説書である。 Boost C++ Libraries は、多くのライブラリの集合体で合うr。 スマートポインタを実装する boost::shared_ptr だけではなく、他にも多くの能力を秘めたライブラリである。 ライブラリを概観するのに、また、API の即時参照に活用できる本である。

こちらも改訂版が出ているようなので、これから購入を検討する場合はそちらの購入をお勧めする。

戻る: C++における基本的なところ