フレンド宣言
フレンド宣言は、クラス本体に現れ、フレンド宣言が現れるクラスのprivateおよびprotectedメンバーへのアクセス権を関数または別のクラスに付与します。
目次 |
[編集] 構文
friend function-declaration |
(1) | ||||||||
friend function-definition |
(2) | ||||||||
friend elaborated-type-specifier ; |
(3) | (C++26まで) | |||||||
friend simple-type-specifier ;
|
(4) | (C++11以降) (C++26まで) | |||||||
friend friend-type-specifier-list ; |
(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; // every B<T> is a friend of A template<typename T> friend void f(T) {} // every f<T> is a friend of A };
フレンド宣言は部分特殊化を参照することはできませんが、完全特殊化を参照することはできます。
template<class T> class A {}; // primary template<class T> class A<T*> {}; // partial template<> class A<int> {}; // full class X { template<class T> friend class A<T*>; // Error friend class A<int>; // OK };
フレンド宣言が関数テンプレートの完全特殊化を参照する場合、キーワード inline(C++11以降)、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); // error: default args not allowed };
テンプレートフレンド宣言は、クラステンプレートAのメンバを名前付けできます。これはメンバ関数またはメンバ型(型は要素化型指定子を使用する必要がある)のいずれかです。このような宣言は、ネスト名指定子の最後のコンポーネント(最後の::の左側の名前)が単純テンプレートID(山括弧内の引数リストに続くテンプレート名)であり、クラステンプレートを名前付けしている場合にのみ有効です。このようなテンプレートフレンド宣言のテンプレートパラメータは、単純テンプレートIDから推論可能である必要があります。
この場合、Aの任意の特殊化またはAの部分特殊化のメンバがフレンドになります。これには、プライマリテンプレートAまたはAの部分特殊化のインスタンス化は含まれません。唯一の要件は、その特殊化からのAのテンプレートパラメータの推論が成功し、推論されたテンプレート引数をフレンド宣言に代入すると、特殊化のメンバの有効な再宣言となる宣言が生成されることです。
// primary template template<class T> struct A { struct B {}; void f(); struct D { void g(); }; T h(); template<T U> T i(); }; // full specialization template<> struct A<int> { struct B {}; int f(); struct D { void g(); }; template<int U> int i(); }; // another full specialization template<> struct A<float*> { int *h(); }; // the non-template class granting friendship to members of class template A class X { template<class T> friend struct A<T>::B; // all A<T>::B are friends, including A<int>::B template<class T> friend void A<T>::f(); // A<int>::f() is not a friend because its signature // does not match, but e.g. A<char>::f() is a friend // template<class T> // friend void A<T>::D::g(); // ill-formed, the last part of the nested-name-specifier, // // D in A<T>::D::, is not simple-template-id template<class T> friend int* A<T*>::h(); // all A<T*>::h are friends: // A<float*>::h(), A<int*>::h(), etc template<class T> template<T U> // all instantiations of A<T>::i() and A<int>::i() are friends, friend T A<T>::i(); // and thereby all specializations of those function templates };
|
デフォルトテンプレート引数は、宣言が定義であり、かつこの翻訳単位にこの関数テンプレートの他の宣言が存在しない場合にのみ、テンプレートフレンド宣言で許可されます。 |
(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; // generates a non-template operator<< for this T 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; // forward declare to make function declaration possible template<typename T> // declaration std::ostream& operator<<(std::ostream&, const Foo<T>&); template<typename T> class Foo { public: Foo(const T& val) : data(val) {} private: T data; // refers to a full specialization for this particular T friend std::ostream& operator<< <> (std::ostream&, const Foo&); // note: this relies on template argument deduction in declarations // can also specify the template argument with operator<< <T>" }; // definition 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以降) 関数または関数テンプレートがフレンド宣言で宣言され、対応する非フレンド宣言が到達可能な場合、その名前は、その先行宣言から決定されるリンケージを持ちます。
それ以外の場合、フレンド宣言によって導入される名前のリンケージは、通常どおり決定されます。
[編集] 注記
フレンドシップは推移的ではありません(友達の友達はあなたの友達ではありません)。
フレンドシップは継承されません(友達の子供はあなたの友達ではなく、あなたの友達はあなたの子供の友達ではありません)。
アクセス指定子はフレンド宣言の意味に影響しません(private:またはpublic:セクションに現れることができ、違いはありません)。
フレンドクラス宣言は新しいクラスを定義できません(friend class X {}; はエラーです)。
ローカルクラスが修飾されていない関数またはクラスをフレンドとして宣言する場合、最も内側の非クラススコープの関数とクラスのみが検索され、グローバル関数は検索されません。
class F {}; int f(); int main() { extern int g(); class Local // Local class in the main() function { friend int f(); // Error, no such function declared in main() friend int g(); // OK, there is a declaration for g in main() friend class F; // friends a local F (defined later) friend class ::F; // friends the global F }; class F {}; // local F }
クラスまたはクラステンプレートX内のフレンド宣言で最初に宣言された名前は、Xの最も内側の囲む名前空間のメンバになりますが、可視ではありません(Xを考慮する引数依存検索を除く)。名前空間スコープでの対応する宣言が提供されていない限り、名前空間の詳細については名前空間を参照してください。
| 機能テストマクロ | 値 | 規格 | 機能 |
|---|---|---|---|
__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のフレンドクラスは、プライベートまたは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 Friends [class.friend]
- 13.7.5 Friends [temp.friend]
- C++20 standard (ISO/IEC 14882:2020)
- 11.9.3 Friends [class.friend]
- 13.7.4 Friends [temp.friend]
- C++17 standard (ISO/IEC 14882:2017)
- 14.3 Friends [class.friend]
- 17.5.4 Friends [temp.friend]
- C++14 standard (ISO/IEC 14882:2014)
- 11.3 Friends [class.friend]
- 14.5.4 Friends [temp.friend]
- C++11 standard (ISO/IEC 14882:2011)
- 11.3 Friends [class.friend]
- 14.5.4 Friends [temp.friend]
- C++98 標準 (ISO/IEC 14882:1998)
- 11.3 Friends [class.friend]
- 14.5.3 Friends [temp.friend]
[編集] 関連項目
| クラス型 | 複数のデータメンバーを持つ型を定義します。 |
| アクセス指定子 | クラスメンバーの可視性を定義します。 |