フレンド宣言
フレンド宣言はクラス本体内に現れ、そのクラスの private または protected メンバへのアクセスを他のクラスまたは関数に許可します。
構文
friend function-declaration
|
(1) | ||||||||
friend function-definition
|
(2) | ||||||||
friend elaborated-class-specifier ;
|
(3) | ||||||||
friend simple-type-specifier ;
|
(4) | (C++11以上) | |||||||
説明
class Y {
int data; // プライベートメンバ。
// 非メンバ関数 operator<< から Y のプライベートメンバへのアクセスを許可します。
friend std::ostream& operator<<(std::ostream& out, const Y& o);
// 他のクラスのメンバともフレンドになれます。
friend char* X::foo(int);
// コンストラクタやデストラクタもフレンドになれます。
friend X::X(char), X::~X();
};
// フレンド宣言はメンバ関数を宣言するわけではありません。
// この operator<< は非メンバとして定義する必要があります。
std::ostream& operator<<(std::ostream& out, const Y& y)
{
return out << y.data; // プライベートメンバ Y::data にアクセスできます。
}
class X {
int a;
friend void friend_set(X& p, int i) { // これは非メンバ関数です。
p.a = i;
}
public:
void member_set(int i) { // これはメンバ関数です。
a = i;
}
};
friend 宣言は無視されます。 この宣言は新しい型を前方宣言しません。
class Y {};
class A {
int data; // プライベートデータメンバ。
class B { }; // プライベートなネストした型。
enum { a = 100 }; // プライベートな列挙子。
friend class X; // フレンドクラスの前方宣言 (複雑型指定子)。
friend Y; // フレンドクラスの宣言 (単純型指定子) (C++11以上)。
};
class X : A::B { // OK、フレンドは A::B にアクセスできます。
A::B mx; // OK、フレンドのメンバは A::B にアクセスできます。
class Y {
A::B my; // OK、フレンドのネストしたメンバは A::B にアクセスできます。
};
int v[A::a]; // OK、フレンドのメンバは A::a にアクセスできます。
};
ノート
フレンドは推移的ではありません (あなたの友達の友達は、あなたの友達ではありません)。
フレンドは継承されません (あなたの友達の子供は、あなたの友達ではありません)。
フレンド関数の宣言では記憶域クラス指定子は使用できません。 フレンド宣言で定義される関数は外部リンケージを持ち、以前に定義された関数はそれが定義されたときのリンケージを維持します。
フレンド宣言ではアクセス指定子は効果がありません (フレンド宣言は private: セクションにも public: セクションにも書けますが、違いはありません)。
フレンドクラスの宣言で新しいクラスは定義できません (friend class X {}; はエラーです)。
ローカルクラスが非修飾名の関数またはクラスをフレンドとして宣言したときは、グローバルな関数ではなく、最も内側の非クラススコープ内の関数およびクラスのみが名前探索されます。
class F {};
int f();
int main()
{
extern int g();
class Local { // main() 関数内のローカルクラス。
friend int f(); // エラー、 main() 内にそのような関数は宣言されていません。
friend int g(); // OK、 main() 内に g の宣言があります。
friend class F; // ローカルな F (後で定義されます) をフレンドにします。
friend class ::F; // グローバルな F をフレンドにします。
};
class F {}; // ローカルな F。
}
クラスまたはクラステンプレート X 内のフレンド宣言で最初に宣言された名前は、 X の最も内側の囲っている名前空間のメンバになりますが、その名前空間スコープで一致する宣言が提供されない限り、 (X を考慮した実引数依存の名前探索除いて) 名前探索に対して可視ではありません。 詳細については名前空間を参照してください。
テンプレートフレンド
関数テンプレートおよびクラステンプレートはどちらも、任意の非ローカルクラスまたはクラステンプレート内に friend 指定子と共に現れることができます (ただしフレンドを許可するクラスまたはクラステンプレート内で定義できるのは関数テンプレートだけです)。 この場合、暗黙に実体化されたか部分特殊化されたか明示的に特殊化されたかに関わらず、そのテンプレートのすべての特殊化がフレンドとなります。
class A {
template<typename T>
friend class B; // すべての B<T> が A のフレンドです。
template<typename T>
friend void f(T) {} // すべての f<T> が A のフレンドです。
};
フレンド宣言は部分特殊化を参照することはできませんが、完全特殊化を参照することはできます。
template<class T> class A {}; // プライマリテンプレート。
template<class T> class A<T*> {}; // 部分特殊化。
template<> class A<int> {}; // 完全特殊化。
class X {
template<class T> friend class A<T*>; // エラー。
friend class A<int>; // OK。
};
フレンド宣言が関数テンプレートの完全特殊化を参照するとき、キーワード inline およびデフォルト引数は使用できません。
template<class T> void f(int);
template<> void f<int>(int);
class X {
friend void f<int>(int x = 1); // エラー、デフォルト引数は許されません。
};
テンプレートフレンド宣言はクラステンプレート A のメンバを表すことができ、それはメンバ関数またはメンバ型のどちらにもできます (型は複雑型指定子を用いなければなりません)。 そのような宣言はその nested-name-specifier の最後の部分 (最後の :: の左の名前) がクラステンプレートを表す simple-template-id (テンプレート名に角括弧で囲った引数リストが続いたもの) である場合にのみ well-formed です。 そのようなテンプレートフレンド宣言のテンプレート引数は simple-template-id から推定できなければなりません。
この場合、 A のあらゆる特殊化のそのメンバがフレンドになります。 これはプライマリテンプレート A の実体化を発生させません。 唯一の要件は、その特殊化の A のテンプレート引数の推定が成功し、その推定したテンプレート引数のフレンド宣言への置き換えがその特殊化のメンバの有効な再宣言となるであろう宣言を生成することです。
// プライマリテンプレート。
template<class T>
struct A {
struct B { };
void f();
struct D { void g(); };
T h();
template<T U> T i();
};
// 完全特殊化。
template<>
struct A<int> {
struct B { };
int f();
struct D { void g(); };
template<int U> int i();
};
// 別の完全特殊化。
template<>
struct A<float*> {
int *h();
};
// クラステンプレート A のメンバにフレンドを許可する非テンプレートクラス。
class X {
template<class T>
friend struct A<T>::B; // すべての A<T>::B (A<int>::B を含む) がフレンドです。
template<class T>
friend void A<T>::f(); // A<int>::f() はシグネチャが一致しないためフレンドではありません
// が、例えば A<char>::f() はフレンドです。
// template<class T>
// friend void A<T>::D::g(); // ill-formed、 nested-name-specifier の最後の部分
// // (A<T>::D:: の D) が simple-template-id ではありません。
template<class T>
friend int* A<T*>::h(); // すべての A<T*>::h (A<float*>::h()、 A<int*>::h()など) がフレンドです。
template<class T>
template<T U> // A<T>::i() のすべての実体化と A<int>::i() がフレンドであり、
friend T A<T>::i(); // そのためそれらの関数テンプレートのすべての特殊化がフレンドです。
};
|
デフォルトテンプレート引数は、その宣言が定義であり、その関数テンプレートのそれ以外の宣言がこの翻訳単位内に現れない場合にのみ、テンプレートフレンド宣言に対して許されます。 |
(C++11以上) |
テンプレートフレンド演算子
テンプレートフレンドの一般的なユースケースは、クラステンプレートに対して動作する非メンバの演算子オーバーロードの宣言です。 例えば、何らかのユーザ定義型 Foo<T> に対する operator<<(std::ostream&, const Foo<T>&) などです。
そのような演算子はクラスの本体内部で定義することができ、それぞれの T に対して別々の非テンプレート operator<< を生成してその非テンプレート operator<< を Foo<T> のフレンドとする効果があります。
#include <iostream>
template<typename T>
class Foo {
public:
Foo(const T& val) : data(val) {}
private:
T data;
// this の T に対して非テンプレート operator<< を生成します。
friend std::ostream& operator<<(std::ostream& os, const Foo& obj)
{
return os << obj.data;
}
};
int main()
{
Foo<double> obj(1.23);
std::cout << obj << '\n';
}
出力:
1.23
さもなければ、関数テンプレートをクラス本体より前にテンプレートとして宣言する必要があります。 この場合、 Foo<T> 内のフレンド宣言は、その T に対する operator<< の完全特殊化を参照します。
#include <iostream>
template<typename T>
class Foo; // 関数宣言を可能とするための前方宣言。
template<typename T> // 宣言。
std::ostream& operator<<(std::ostream&, const Foo<T>&);
template<typename T>
class Foo {
public:
Foo(const T& val) : data(val) {}
private:
T data;
// this の特定の T に対する完全特殊化を参照します。
friend std::ostream& operator<< <> (std::ostream&, const Foo&);
// ノート: この宣言はテンプレートの実引数推定を利用しています。
// 「operator<< <T>」のようにテンプレート引数を指定することもできます。
};
// 定義。
template<typename T>
std::ostream& operator<<(std::ostream& os, const Foo<T>& obj)
{
return os << obj.data;
}
int main()
{
Foo<double> obj(1.23);
std::cout << obj << '\n';
}
例
ストリーム挿入演算子および抽出演算子はしばしば非メンバのフレンドとして宣言されます。
#include <iostream>
#include <sstream>
class MyClass {
int i;
friend std::ostream& operator<<(std::ostream& out, const MyClass& o);
friend std::istream& operator>>(std::istream& in, MyClass& o);
public:
MyClass(int i = 0) : i(i) {}
};
std::ostream& operator<<(std::ostream& out, const MyClass& mc)
{
return out << mc.i;
}
std::istream& operator>>(std::istream& in, MyClass& mc)
{
return in >> mc.i;
}
int main()
{
MyClass mc(7);
std::cout << mc << '\n';
std::istringstream("100") >> mc;
std::cout << mc << '\n';
}
出力:
7
100
欠陥報告
以下の動作変更欠陥報告は以前に発行された C++ 標準に遡って適用されました。
| DR | 適用先 | 発行時の動作 | 正しい動作 |
|---|---|---|---|
| CWG 45 | C++98 | members of a class nested in a friend class of T have no special access to T
|
a nested class has the same access as the enclosing class |
| CWG 500 | C++98 | friend class of T cannot inherit from private or protected members of T, but its nested class can
|
both can inherit from such members |
参考文献
- C++11 standard (ISO/IEC 14882:2011):
- 11.3 Friends [class.friend]
- 14.5.4 Friends [temp.friend]
- C++98 standard (ISO/IEC 14882:1998):
- 11.3 Friends [class.friend]
- 14.5.3 Friends [temp.friend]
関連項目
| クラス宣言 | |
| アクセス指定子 |