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