命名空间
变体
操作

友元声明

来自 cppreference.com
< cpp‎ | 语言
 
 
C++ 语言
一般主题
流程控制
条件执行语句
if
迭代语句(循环)
for
范围-for (C++11)
跳转语句
函数
函数声明
Lambda 函数表达式
inline 说明符
动态异常规范 (直到 C++17*)
noexcept 说明符 (C++11)
异常
命名空间
类型
说明符
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
存储持续时间说明符
初始化
表达式
备用表示法
字面量
布尔值 - 整数 - 浮点数
字符 - 字符串 - nullptr (C++11)
用户定义的 (C++11)
实用程序
属性 (C++11)
类型
typedef 声明
类型别名声明 (C++11)
强制转换
内存分配
访问说明符
friend 说明符

类特定的函数属性
explicit (C++11)
static

特殊成员函数
模板
其他
 
 

友元声明出现在 类体 中,并授予函数或另一个类访问声明友元声明的类的私有和受保护成员的权限。

内容

[编辑] 语法

friend function-declaration (1)
friend function-definition (2)
friend elaborated-type-specifier ; (3) (直到 C++26)
friend simple-type-specifier ;

friend typename-specifier ;

(4) (自 C++11 起)
(直到 C++26)
friend friend-type-specifier-list ; (5) (自 C++26 起)
1,2) 友元函数声明。
3-5) 友元类声明。
function-declaration - 一个 函数声明
function-definition - 一个 函数定义
elaborated-type-specifier - 一个 详细类型说明符
simple-type-specifier - 一个 简单类型说明符
typename-specifier - 关键字 typename 后跟一个限定标识符或限定的 简单模板标识符
friend-type-specifier-list - 一个非空的逗号分隔的 simple-type-specifierelaborated-type-specifiertypename-specifier s 列表,每个说明符后面都可以跟省略号 (...)

[编辑] 描述

1) 将一个函数或多个函数指定为该类的友元
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
}
2) (仅允许在非 局部 类定义中)定义一个非成员函数,并同时将其设为该类的友元。此类非成员函数始终为 内联,除非它附加到一个 命名模块(自 C++20 起)
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
    }
};
3,4) 将一个类指定为该类的友元。这意味着友元的成员声明和定义可以访问该类的私有和受保护成员,并且友元也可以从该类的私有和受保护成员继承。
3) 类由 elaborated-type-specifier 命名。在此友元声明中使用的类名不需要事先声明。
4) 类由 simple-type-specifiertypename-specifier 命名。如果命名的类型不是类类型,则此友元声明将被忽略。此声明不会转发声明新类型。
5)friend-type-specifier-list 中的所有类指定为该类的友元。这意味着友元的成员声明和定义可以访问该类的私有和受保护成员,并且友元也可以从该类的私有和受保护成员继承。如果命名的类型不是类类型,则它在此友元声明中会被忽略。
friend-type-specifier-list 中的每个说明符都命名一个类,如果说明符后没有省略号,否则应用 包展开
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
};

[edit] 模板友元

函数模板类模板 声明都可以在任何非局部类或类模板中使用 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 的成员,该成员可以是成员函数或成员类型(类型必须使用 详细类型说明符)。此类声明只有在嵌套名称说明符中的最后一个组件(最后一个 :: 左边的名称)是命名类模板的简单模板标识符(模板名称后跟尖括号中的参数列表)时才有效。此类模板友元声明的模板参数必须从简单模板标识符中推断出来。

在这种情况下,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 起)

[edit] 模板友元运算符

模板友元的常见用例是声明作用于类模板的非成员运算符重载,例如 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> 中的友元声明可以引用 operator<< 针对其 T 的完全特化

#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';
}

[edit] 链接

友元声明中不允许使用 存储类说明符

如果函数或函数模板首先在友元声明中声明和定义,并且封闭类在 导出声明 中定义,则其名称具有与封闭类名称相同的链接。

(自 C++20 起)

如果(直到 C++20)否则,如果(自 C++20 起) 函数或函数模板在友元声明中声明,并且可以访问 相应的非友元声明,则该名称具有从该先前声明确定的链接。

否则,由友元声明引入的名称的链接将按通常方式确定。

[edit] 说明

友谊不是传递的(你朋友的朋友不是你的朋友)。

友谊不是继承的(你朋友的孩子不是你的朋友,你的朋友也不是你孩子的朋友)。

访问说明符 对友元声明的含义没有影响(它们可以出现在 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) 可变参数友元声明

[edit] 关键字

friend

[edit] 示例

流插入和提取运算符通常声明为非成员友元

#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

[edit] 缺陷报告

以下行为改变的缺陷报告已追溯应用于以前发布的 C++ 标准。

DR 应用于 已发布的行为 正确的行为
CWG 45 C++98 嵌套在友元中的类的成员
T 的类对 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 由友元声明引入的名称的链接不清楚 已澄清

[edit] 参考文献

  • 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]

[edit] 另请参阅

类类型 定义包含多个数据成员的类型 [edit]
访问说明符 定义类成员的可见性 [edit]