ikautak.log

C/C++, Python, CUDA, Android, Linux kernel, Network, etc.

More Effective C++ 項目28 スマートポインタ

新訂版 More Effective C++ (AddisonーWesley professional co)

新訂版 More Effective C++ (AddisonーWesley professional co)

  • 作者: スコット・メイヤーズ,安村通晃,伊賀聡一郎,飯田朱美,永田周一
  • 出版社/メーカー: ピアソンエデュケーション
  • 発売日: 2007/06/29
  • メディア: 単行本(ソフトカバー)
  • 購入: 8人 クリック: 129回
  • この商品を含むブログ (43件) を見る

項目28 スマートポインタ

スマートポインタは、ただの組込みポインタのように見え、より多くの機能を備えたもの。
以下の振る舞いを管理できる。

  • 生成と破棄
    生成されたときと、破棄されたときに起こることを決められる。
    たいていは破棄されるときに指しているオブジェクトを削除する。
  • コピーと代入
    コピーされたり代入された場合に起こることを制御できる。
    deep copyするのか、ポインタのみがコピーされるのか、コピー禁止なのか。
  • 参照はがし(dereferencing)
    クライアントがスマートポインタに指されているオブジェクトを参照した場合(*, ->演算子)の制御。
スマートポインタの生成、代入、破棄

生成は、指される先のオブジェクトを指定して、スマートポインタ内部のポインタがそこを指すようにする。
代入、デストラクタを実装するのは、指し示すオブジェクトの所有権を持っているかどうかで複雑になる。

参照回数計測を使うスマートポインタの場合は、指し示すオブジェクトを削除するかどうかの前に、参照回数を調節する必要がある。

参照はがし演算子を実装する

operator*とoperator->の処理は以下のようになる。

template<class T>
T& SmartPtr<T>::operator*() const
{
    // スマートポインタの処理(遅延フェッチならデータを読みだすとか)

    return *pointee;
}

最初はpointeeを有効にするための処理を実行し、pointeeが有効であればオブジェクトの参照を返すだけ。

スマートポインタがnullかどうかテストする

普通のポインタのように書くとエラーになってしまう。

SmartPtr<TreeNode> ptn;
...
if (ptn == 0) ... // NG
if (ptn) ...      // NG
if (!ptn) ...     // NG

その場合はvoid*の変換を使う。

template<class T>
class SmartPtr {
public:
    ...
    operator void*();
    ...
};

SmartPtr<Node> ptn;
...
if (ptn == 0) ... // OK
if (ptn) ...      // OK
if (!ptn) ...     // OK

普通のポインタのように書けるが、全く異なる型のスマートポインタも比較可能になってしまうため、operator!をオーバーロードして、スマートポインタがnullの場合にtrueを返すようにする。

スマートポインタをただのポインタに変換する

スマートポインタのテンプレートにTへのただのポインタへの変換演算子を入れる。

template<class T>
class DBPtr {
public:
    ...
    operator T*() { return pointee; }
    ...
};

この関数を加えることでnullであるかどうかもテストできる。

DBPtr(Tuple> pt;
...
if (pt == 0) ... // OK. ptをTuple*に変換する
if (pt) ...      // OK
if (!pt) ...     // OK

ただ、このoperator T*()関数によりクライアントが直接ポインタを操作しやすくなってしまう。

スマートポインタと継承ベースの型変換

スマートポインタに入れるオブジェクトに継承関係があっても、スマートポインタにはないので、以下の場合はコンパイルできない。

class A {
    ...
};

class B : public A {
    ...
};

class C : public A {
    ...
};

void print(const SmartPtr<A>& a);

SmartPtr<B> b(new B());
SmartPtr<C> c(new C());

print(b); // NG! SmartPtr<A>には変換できない
print(c); // NG! SmartPtr<A>には変換できない

一応SmartPtr<B>を特殊化してStartPtr<A>へ変換するためのoperatorを書けば回避できるが、手動で追加する必要があるし、階層をたどって変換演算子をたくさん追加しなくてはならなくなる。
メンバー関数テンプレートを使うと何とかなるが、非常にわかりづらくなるのでオススメではない。

スマートポインタとconst

ただのポインタと同じように

A a;

const A* p;             // const A型へのポインタ
A* const p = &a;        // A型へのconstポインタ(要初期化)
const A * const p = &a; // const A型へのconstポインタ(要初期化)

のように書きたい場合、SmartPtr<A>にSmartPtr<const A>への変換関数を提供するとできる。

また、const Tへのスマートポインタを継承し、非const Tへのスマートポインタを作る方法もある。