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 の特殊化ごとに生成される。

既知の利用

編集

関連するイディオム

編集

References

編集