命名空间
变体
操作

友元声明

来自 cppreference.cn
< cpp‎ | language
 
 
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)
存储期说明符
初始化
 
 

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

目录

[编辑] 语法

friend 函数声明 (1)
friend 函数定义 (2)
friend 详述类型说明符 ; (3) (直到 C++26)
friend 简单类型说明符 ;

friend 类型名说明符 ;

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

[编辑] 描述

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) 该类由 详述类型说明符 命名。在此友元声明中使用的类的名称不需要事先声明。
4) 该类由 简单类型说明符类型名说明符 命名。如果命名的类型不是类类型,则此友元声明将被忽略。此声明不会前向声明新类型。
5) 指定 友元类型说明符列表 中的所有类作为此类的友元。这意味着友元的成员声明和定义可以访问此类的私有和受保护成员,并且友元也可以从此类的私有和受保护成员继承。如果命名的类型不是类类型,则在此友元声明中将其忽略。
如果 友元类型说明符列表 中的每个说明符后未跟省略号,则命名一个类,否则应用包展开
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
};

当友元声明引用函数模板的完全特化时,关键字 inlineconstexpr(自 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 起)

[编辑] 模板友元运算符

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

#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 的实参依赖查找)是不可见的,除非提供了命名空间作用域中的匹配声明 - 请参阅命名空间以了解详细信息。

特性测试宏 Std 特性
__cpp_variadic_friend 202403L (C++26) 可变参数友元声明

[编辑] 关键字

friend

[编辑] 示例

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

#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 友元中嵌套的类的成员
TT 没有特殊访问权限
嵌套类具有相同的
访问权限,如同封闭类
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]

[编辑] 参见

类类型 定义持有多个数据成员的类型 [编辑]
访问说明符 定义类成员的可见性[编辑]