Friend declaration
friend宣言は クラス本体 内に現れ、friend宣言が現れるクラスのprivateおよびprotectedメンバーに関数または他のクラスがアクセスすることを許可します。
目次 |
構文
friend
関数宣言
|
(1) | ||||||||
friend
関数定義
|
(2) | ||||||||
friend
詳細型指定子
;
|
(3) | (C++26まで) | |||||||
friend
単純型指定子
;
|
(4) |
(C++11以降)
(C++26まで) |
|||||||
friend
フレンド型指定子リスト
;
|
(5) | (C++26以降) | |||||||
| function-declaration | - | 関数宣言 |
| function-definition | - | 関数定義 |
| elaborated-type-specifier | - | 詳細型指定子 |
| simple-type-specifier | - | 単純型指定子 |
| typename-specifier | - | キーワード typename に続く修飾付き識別子、または修飾付き 単純テンプレート識別子 |
| friend-type-specifier-list | - |
空でないカンマ区切りの
simple-type-specifier
、
elaborated-type-specifier
、および
typename-specifier
のリスト。各指定子には省略記号(
...
)を続けることができる
|
説明
class Y { int data; // private member // the non-member function operator<< will have access to Y's private members friend std::ostream& operator<<(std::ostream& out, const Y& o); friend char* X::foo(int); // members of other classes can be friends too friend X::X(char), X::~X(); // constructors and destructors can be friends }; // friend declaration does not declare a member function // this operator<< still needs to be defined, as a non-member std::ostream& operator<<(std::ostream& out, const Y& y) { return out << y.data; // can access private member Y::data }
class X { int a; friend void friend_set(X& p, int i) { p.a = i; // this is a non-member function } public: void member_set(int i) { a = i; // this is a member function } };
class Y {}; class A { int data; // private data member class B {}; // private nested type enum { a = 100 }; // private enumerator friend class X; // friend class forward declaration (elaborated class specifier) friend Y; // friend class declaration (simple type specifier) (since C++11) // the two friend declarations above can be merged since C++26: // friend class X, Y; }; class X : A::B // OK: A::B accessible to friend { A::B mx; // OK: A::B accessible to member of friend class Y { A::B my; // OK: A::B accessible to nested member of friend }; int v[A::a]; // OK: A::a accessible to member of friend };
テンプレートフレンド
関数テンプレートとクラステンプレートの宣言は、非ローカルクラスまたはクラステンプレート内で
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 , constexpr (C++11以降) , consteval (C++20以降) およびデフォルト引数は使用できません:
template<class T> void f(int); template<> void f<int>(int); class X { friend void f<int>(int x = 1); // エラー: デフォルト引数は許可されていません };
テンプレートのフレンド宣言は、クラステンプレートAのメンバー(メンバー関数またはメンバー型)を指定できます(型は
elaborated-type-specifier
を使用する必要があります)。このような宣言は、そのネストされた名前指定子の最後の構成要素(最後の
::
の左側の名前)がクラステンプレートを指定するsimple-template-id(テンプレート名と山括弧内の引数リスト)である場合にのみ適切な形式となります。このようなテンプレートフレンド宣言のテンプレートパラメータは、simple-template-idから推論可能でなければなりません。
この場合、Aの任意の特殊化またはAの部分特殊化のメンバーはフレンドとなります。これは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(); // 不適格、ネストされた名前指定子の最後の部分 // // A<T>::D:: のDが単純テンプレート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以降) |
テンプレートフレンド演算子
テンプレートフレンドの一般的な使用例は、クラステンプレートに対して動作する非メンバ演算子オーバーロードの宣言です。例えば、 operator << ( std:: ostream & , const Foo < T > & ) のような演算子を、ユーザー定義の Foo < T > に対して宣言する場合です。
このような演算子はクラス本体内で定義でき、これにより各
T
に対して個別の非テンプレート
operator
<<
が生成され、その非テンプレート
operator
<<
が自身の
Foo
<
T
>
のフレンドとなる効果があります:
#include <iostream> template<typename T> class Foo { public: Foo(const T& val) : data(val) {} private: T data; // この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; // この特定の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'; }
リンケージ
ストレージクラス指定子 はフレンド宣言では使用できません。
|
関数または関数テンプレートがフレンド宣言で最初に宣言および定義され、かつ外側のクラスが エクスポート宣言 内で定義されている場合、その名前は外側のクラスの名前と同じリンケージを持つ。 |
(C++20以降) |
もし (C++20まで) それ以外の場合、 (C++20以降) 関数または関数テンプレートがフレンド宣言で宣言され、かつ 対応する非フレンド宣言 が到達可能である場合、その名前は先行する宣言から決定されたリンケージを持つ。
それ以外の場合、フレンド宣言によって導入された名前のリンケージは通常通り決定されます。
注記
フレンド関係は推移的ではありません(あなたの友達の友達はあなたの友達ではありません)。
フレンドシップは継承されません(あなたの友人の子供はあなたの友人ではなく、あなたの友人もあなたの子供の友人ではありません)。
Access specifiers はフレンド宣言の意味に影響を与えません(それらは 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
内のfriend宣言で最初に宣言された名前は、
X
の最も内側の外側の名前空間のメンバーになりますが、名前空間スコープでの一致する宣言が提供されない限り、ルックアップには見えません(
X
を考慮する実引数依存ルックアップを除く)。詳細は
namespaces
を参照してください。
| 機能テストマクロ | 値 | 規格 | 機能 |
|---|---|---|---|
__cpp_variadic_friend
|
202403L
|
(C++26) | 可変長フレンド宣言 |
キーワード
例
ストリーム挿入演算子と抽出演算子は、非メンバーフレンド関数として宣言されることが多い:
#include <iostream> #include <sstream> class MyClass { int i; // friends have access to non-public, non-static static inline int id{6}; // and static (possibly inline) members friend std::ostream& operator<<(std::ostream& out, const MyClass&); friend std::istream& operator>>(std::istream& in, MyClass&); friend void change_id(int); public: MyClass(int i = 0) : i(i) {} }; std::ostream& operator<<(std::ostream& out, const MyClass& mc) { return out << "MyClass::id = " << MyClass::id << "; i = " << mc.i; } std::istream& operator>>(std::istream& in, MyClass& mc) { return in >> mc.i; } void change_id(int id) { MyClass::id = id; } int main() { MyClass mc(7); std::cout << mc << '\n'; // mc.i = 333*2; // error: i is a private member std::istringstream("100") >> mc; std::cout << mc << '\n'; // MyClass::id = 222*3; // error: id is a private member change_id(9); std::cout << mc << '\n'; }
出力:
MyClass::id = 6; i = 7 MyClass::id = 6; i = 100 MyClass::id = 9; i = 100
不具合報告
以下の動作変更の欠陥報告書は、以前に公開されたC++規格に対して遡及的に適用されました。
| DR | 適用対象 | 公開時の動作 | 正しい動作 |
|---|---|---|---|
| CWG 45 | C++98 |
T
のフレンドクラス内にネストされたクラスのメンバーは
T
への特別なアクセス権を持たない
|
ネストされたクラスは外側のクラスと
同じアクセス権を持つ |
| CWG 500 | C++98 |
T
のフレンドクラスは
T
のprivateまたはprotectedメンバーから
継承できないが、そのネストされたクラスは継承できる |
両者ともそのようなメンバーから
継承可能 |
| CWG 1439 | C++98 |
非ローカルクラス内のフレンド宣言を対象とするルールが
テンプレート宣言をカバーしていなかった |
カバー対象に含まれる |
| CWG 1477 | C++98 |
クラスまたはクラステンプレート内のフレンド宣言で最初に宣言された
名前は、マッチする宣言が他の名前空間スコープで提供されている場合、 ルックアップで可視ではなかった |
この場合でも
ルックアップで可視 |
| CWG 1804 | C++98 |
クラステンプレートのメンバーがフレンド指定された場合、
そのクラステンプレートの部分特殊化の特殊化における 対応するメンバーはフレンド指定されなかった |
そのようなメンバーも
フレンドとなる |
| CWG 2379 | C++11 |
関数テンプレートの完全特殊化を参照するフレンド宣言を
constexprで宣言できた |
禁止 |
| CWG 2588 | C++98 | フレンド宣言によって導入される名前のリンケージが不明確だった | 明確化された |
参考文献
- C++23規格 (ISO/IEC 14882:2024):
-
- 11.8.4 フレンド [class.friend]
-
- 13.7.5 フレンド [temp.friend]
- C++20標準 (ISO/IEC 14882:2020):
-
- 11.9.3 フレンド [class.friend]
-
- 13.7.4 フレンド [temp.friend]
- C++17標準 (ISO/IEC 14882:2017):
-
- 14.3 フレンド [class.friend]
-
- 17.5.4 フレンド [temp.friend]
- C++14標準 (ISO/IEC 14882:2014):
-
- 11.3 フレンド [class.friend]
-
- 14.5.4 フレンド [temp.friend]
- C++11標準 (ISO/IEC 14882:2011):
-
- 11.3 フレンド [class.friend]
-
- 14.5.4 フレンド [temp.friend]
- C++98標準 (ISO/IEC 14882:1998):
-
- 11.3 フレンド [class.friend]
-
- 14.5.3 フレンド [temp.friend]
関連項目
| Class types | 複数のデータメンバーを保持する型を定義します |
| Access specifiers | クラスメンバーの可視性を定義します |