友元宣告
友元宣告出現在類體中,並授予函式或其他類訪問該類(其中友元宣告出現)的私有和保護成員的許可權。
目錄 |
[編輯] 語法
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、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 命名了類模板時,此類宣告才是格式良好的。此類模板友元宣告的模板引數必須可以從簡單模板 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 起) |
[編輯] 模板友元運算子
模板友元的一個常見用例是宣告作用於類模板的非成員運算子過載,例如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; // 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++ 標準。
缺陷報告 | 應用於 | 釋出時的行為 | 正確的行為 |
---|---|---|---|
CWG 45 | C++98 | 巢狀在友元中的類的成員 類 T 沒有特殊的訪問許可權 |
巢狀類具有相同的 與外圍類相同的訪問許可權 |
CWG 500 | C++98 | 類T 的友元類不能從T 的私有或保護成員繼承,但其巢狀類可以 |
兩者都可以從 此類成員繼承 |
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]
[編輯] 另請參閱
類型別 | 定義持有多個數據成員的型別 |
訪問說明符 | 定義類成員的可見性 |