More C++ Idioms/メンバテンプレートによる型変換(Coercion by Member Template)

メンバテンプレートによる型変換(Coercion by Member Template)
編集

意図 編集

クラステンプレートの型パラメータに対してのみ可能な暗黙の型変換(coercion)と 同じ型変換を、テンプレートクラスに対して行うことを可能にすることによって、 クラステンプレートのインタフェースの自由度を向上させる。

別名 編集

動機 編集

ある 2 つの型の関係を、それらの型によってインスタンス化されたテンプレートクラス型に対しても「拡張」することは しばしば有益である。例えば、クラス D がクラス B から派生されているとしよう。その場合、D 型のオブジェクトへのポインタは、B 型へのポインタに代入可能である。これらの意味論は C++ 言語規格によって暗黙的にサポートされている。 残念ながら、これらの型の複合型は、その基となった型の関係を共有しない。このルールはテンプレートに対しても 同様に適用されるため、一般的には、Helper<D> は Helper<B> に代入可能ではない。

class B {};
class D : public B {};
template <class T>
class Helper {};

B *bptr;
D *dptr;
bptr = dptr; // 規格によって認められている

Helper<B> hb;
Helper<D> hd; 
hb = hd; // これは認められていないが、非常に有益になりうる

このルールに対する例外を認めた方が、まったく例外を認めない場合よりも、より多くの利益と自由度を もたらす場合がある。 例えば、auto_ptr<D> から auto_ptr<B> への変換は認めたいだろう。 これは非常に直感に即した挙動だが、メンバテンプレートによる型変換(coercion by member template)イディオムの利用なしには規格によってサポートされない。

解法とサンプルコード 編集

クラステンプレート内で独立したメンバテンプレート関数を定義し、パラメータ型によってサポートされている 暗黙の型変換をメンバテンプレートの実装中で利用する。

template <class T>
class Ptr
{
  public:
    Ptr () {}

    Ptr (Ptr const & p)
      : ptr (p.ptr)
    {
      std::cout << "これはコピーコンストラクタ\n";
    }

    // メンバテンプレートコンストラクタを利用して型変換(coercion)をサポートする
    template <class U>
    Ptr (Ptr <U> const & p)
      : ptr (p.ptr) // U から T への暗黙の変換が認められていなければならない
    {
      std::cout << "変換に便利なメンバテンプレートコンストラクタは"
                   "コピーコンストラクタではない\n";
    }

    // メンバコピー代入演算子
    Ptr & operator = (Ptr const & p)
    {
      ptr = p.ptr;
      std::cout << "メンバコピー代入演算子\n";
      return *this;
    }

    // メンバテンプレート代入演算子を利用して型変換(coercion)をサポートする
    template <class U>
    Ptr & operator = (Ptr <U> const & p)
    {
      ptr = p.ptr; // U から T への暗黙の変換が認められていなければならない
      std::cout << "変換に便利なメンバテンプレート代入演算子は"
                   "コピー代入演算子ではない\n";
      return *this;
    } 

    T *ptr;
};
int main (void)
{
   Ptr <D> d_ptr;
   Ptr <B> b_ptr (d_ptr); // もはや可能
   b_ptr = d_ptr;         // これもまた可能

}

クラス D がクラス B である (is-a の関係にある)としても、D 型オブジェクトの配列は B 型オブジェクトの配列ではない(is-a の関係にはない)。 これはスライシングの問題のために、C++ 規格によって認められていない。 このルールをポインタの配列に対してゆるめることは、きわめて頻繁に大きな自由度をもたらす。 例えば、D へのポインタの配列は、B へのポインタの配列に代入可能であるとよい。 このイディオムはそれを可能とする。ただし、派生型のオブジェクトの配列を、 基本型のオブジェクトの配列にコピーできないようにするよう格別の注意を払うべきである。 その実現に、メンバテンプレート関数の部分特殊化を用いることができる。以下の例では、U * に対する部分特殊化を ポインタの配列のコピーのみを許すために使用している。

template <class T>
class Array
{
  public:
    Array () {}
    Array (Array const & a)
    {
      std::copy (a.array_, a.array_ + SIZE, array_);
    }

    template <class U>
    Array (Array <U *> const & a)
    {
      std::copy (a.array_, a.array_ + SIZE, array_);
    }

    template <class U>
    Array & operator = (Array <U *> const & a)
    {
      std::copy (a.array_, a.array_ + SIZE, array_);
    }

    enum {  SIZE = 10 };
    T array_[SIZE];
};

標準の auto_ptr や boost の shared_ptr のように、多くのスマートポインタがこのイディオムを用いている。

注意 編集

メンバテンプレートによる型変換イディオムの実装における典型的な誤りは、 非テンプレート版を提供することなしに、テンプレートコピーコンストラクタやテンプレート代入演算子を 提供しようとすることである。

非テンプレート版が提供されていないならば、コンパイラは自動的にデフォルトのコピーコンストラクタと コピー代入演算子の両方あるいは一方を生成するだろう。そして隠れた明白ではない障害を引き起こすかもしれない。

既知の利用 編集

  • std::auto_ptr
  • boost::shared_ptr

関連するイディオム 編集

References 編集