More C++ Idioms/friend 関数の生成(Making New Friends)
friend 関数の生成(Making New Friends)
[編集]意図
[編集]クラステンプレートに対する friend 関数の作成を単純化する。
動機
[編集]friend 関数は、あるクラスの補助的な追加インタフェースを提供するために使われることが多い。 例えば、ストリームへの挿入演算子(<<)、ストリームからの抽出演算子(>>)、多重定義された算術的な演算子などはしばしば friend 関数である。 クラステンプレートの friend 関数を宣言することは、非テンプレートクラスに対する friend 関数を宣言することに比べて、少し複雑である。 テンプレートが含まれる場合、クラスとその friend 関数について 4 種類の関係がありうる。
- 1 対多: 単一の非テンプレート関数がテンプレートクラスのインスタンス全てに対して friend 関数となる。
- 多対 1: テンプレート関数のインスタンス全てが、単一の非テンプレートクラスに対する friend 関数となる。
- 1 対 1: 1組のテンプレート引数によってインスタンス化された単一のテンプレート関数が、同じ 1 組のテンプレート引数によってインスタンス化された単一のテンプレートクラスの friend 関数となる。通常の非テンプレートクラスと通常の非テンプレート friend 関数間の関係もこの分類である。
- 多対多: テンプレート関数のインスタンス全てがテンプレートクラスのインスタンス全ての friend 関数となる。
C++ で 1 対 1 の関係を設定するためには追加の記述が必要であるため、この 1 対 1 関係がここでの主題である。例を以下に示す。
template<typename T>
class Foo {
T value;
public:
Foo(const T& t) value(t) {}
friend ostream& operator<<(ostream&, const Foo<T>&);
};
template<typename T>
ostream& operator<<(ostream os, const Foo<T> b) {
return os << b.value;
}
挿入子(inserter)がテンプレートではないのにテンプレート引数(T)を使用しているため、上記の例は役に立たない(訳註:friend 宣言で指定されているのは非テンプレートの operator<<() であり、下で定義されている関数テンプレートの operator<<() ではない。従って、仮に関数テンプレートがインスタンス化されたとすると、private メンバへのアクセスができずエラーが発生するだろう。また、そもそも引数の型が完全に一致する場合、非テンプレート関数が優先されるため下の関数テンプレートのインスタンスは呼び出されない。従って実際には operator<<() が見つからない、というリンカエラーが発生する)。 これはメンバ関数でないために起こる問題である。 各 T に対して別々の特殊化が作成されるように operator<<() はテンプレートでなければならない。
解法の一つとして、挿入演算子のテンプレートを friend 宣言の前にクラスの外側で宣言し、friend 宣言に <> を付加することがある。 これにより、先に宣言されたテンプレートを friend にするという指定になる。
// 前方宣言
template<class T> class Foo;
template<class T> ostream& operator<<(ostream&,
const Foo<T>&);
template<class T>
class Foo {
T value;
public:
Foo(const T& t) : value(t) {}
friend ostream& operator<< <>(ostream&, const Foo<T>&);
};
template<class T>
ostream& operator<<(ostream& os, const Foo<T>& b)
{
return os << b.value;
}
上記解法の欠点は、非常に冗長な点である。
解法とサンプルコード
[編集]Dan Saks が、上記解法の冗長性を打破する別の方法を示唆した。 彼の解法は、friend 関数の生成(Making New Friends)イディオムとして知られている。 下記のように、クラステンプレートの内部で friend 関数を定義するという発想である。
template<typename T>
class Foo {
T value;
public:
Foo(const T& t) : value(t) {}
friend ostream& operator<<(ostream& os, const Foo<T>& b)
{
return os << b.value;
}
};
そのような friend 関数はテンプレートではないが、新しい friend 関数を「生成」するための工場(factory)としてテンプレートのようなものとなる。 新しい非テンプレート関数が Foo の特殊化ごとに生成される。