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