友元声明
友元声明出现在类体中,并授予函数或其他类访问该类(其中友元声明出现)的私有和保护成员的权限。
目录 |
[编辑] 语法
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]
[编辑] 另请参阅
类类型 | 定义持有多个数据成员的类型 |
访问说明符 | 定义类成员的可见性 |