友元声明

来自cppreference.com
< cpp‎ | language

友元声明在类体内出现,并向一个函数或另一个类授予对包含友元声明的类的私有及受保护成员的访问权。

语法

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

friend typename说明符 ;

(4) (C++11 起)

描述

1) 指明一个或多个函数作为此类的友元:
class Y
{
    int data; // 私有成员
 
    // 非成员函数的运算符 operator<< 将拥有对 Y 的私有成员的访问权
    friend std::ostream& operator<<(std::ostream& out, const Y& o);
    friend char* X::foo(int); // 其他类的成员也可以是友元
    friend X::X(char), X::~X(); // 构造函数与析构函数也可以是友元
};
 
// 友元声明不声明成员函数
// 此 operator<< 作为非成员仍需定义
std::ostream& operator<<(std::ostream& out, const Y& y)
{
    return out << y.data; // 可访问私有成员 Y::data
}
2) (只允许在非局部类的定义中使用)定义一个非成员函数,同时让它成为此类的友元。这种非成员函数始终是内联函数,除非它附着到一个具名模块 (C++20 起)
class X
{
    int a;
 
    friend void friend_set(X& p, int i)
    {
        p.a = i; // 这是非成员函数
    }
public:
    void member_set(int i)
    {
        a = i; // 这是成员函数
    }
};
3) 指定以 详述类说明符(见详述类型说明符)指名的 class、struct 或 union 作为此类的友元。这意味着该友元的各成员声明和定义可以访问此类的私有与受保护成员,而且该友元关系也能被此类的私有或受保护成员继承到其派生类。 不需要提前声明用于此 friend 声明的类名。
4) 指定以 简单类型说明符typename说明符 指名的类型,如果该类型是(可有 cv 限定的)class、struct 或 union,那么该类型是此类的友元;否则忽略该 friend 声明。此声明不会前置声明新类型。
class Y {};
 
class A
{
    int data; // 私有数据成员
 
    class B {}; // 私有嵌套类型
 
    enum { a = 100 }; // 私有枚举项
 
    friend class X; // 友元类的前置声明(详述类型说明符)
    friend Y; // 友元类声明(简单类型说明符) (C++11 起)
};
 
class X : A::B // OK:友元能访问 A::B
{
    A::B mx; // OK:友元的成员能访问 A::B
 
    class Y
    {
        A::B my; // OK:友元的嵌套成员能访问 A::B
    };
 
    int v[A::a]; // OK:友元的成员能访问 A::a
};

注解

友元关系不传递(你朋友的朋友不是你的朋友)。

友元关系不继承(你朋友的父亲不是你的朋友,你父亲的朋友不是你的朋友)。

友元函数声明中不允许使用存储类说明符。在友元声明中定义的函数具有外部链接,先前已定义的函数保持其定义时所具有的链接。

访问说明符对于友元声明的含义没有影响(它们可以在 private:public: 区间出现,且没有区别)。

友元类声明不能定义新的类(friend class X {}; 是错的)。

当局部类将一个无限定的函数或类声明为其友元时,只查找在其最内层非类作用域中的函数与类,而非全局函数:

class F {};
 
int f();
 
int main()
{
    extern int g();
 
    class Local // main() 函数中的局部类
    {
        friend int f(); // 错误,main() 中没有声明该函数
        friend int g(); // OK,main() 中有 g 的声明
        friend class F; // 令局部 F(随后定义)为友元
        friend class ::F; // 令全局 F 为友元
    };
 
    class F {}; // 局部 F
}

首次在类或类模板 X 中的友元声明中被声明的名字会成为 X 的最内层外围命名空间的成员,但它对于查找不可见(除了考虑 X 的实参依赖查找),除非在命名空间作用域中提供与之匹配的声明——细节见命名空间

模板友元

函数模板类模板声明,都可带 friend 说明符出现于任何非局部类或类模板内(尽管只有函数模板能在授予友元关系的类或类模板内进行定义)。这种情况下,该模板的每个特化都成为友元,不管是被隐式实例化、部分特化或显式特化。

class A
{
    template<typename T>
    friend class B; // 每个 B<T> 都是 A 的友元
 
    template<typename T>
    friend void f(T) {} // 每个 f<T> 都是 A 的友元
};

友元声明不能指代部分特化,但能指代全特化:

template<class T>
class A {};      // 主模板
 
template<class T>
class A<T*> {};  // 部分特化
 
template<>
class A<int> {}; // 全特化
 
class X
{
    template<class T>
    friend class A<T*>;  // 错误!
 
    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); // 错误:不允许默认实参
};

模板友元声明可以指名类模板 A 的成员,它可以是成员函数或成员类型(类型必须用详述类型说明符)。这种声明只有在它的 嵌套名说明符 中的最后组分(最后的 :: 左边的名字)是一个指名该类模板的 简单模板标识(模板名后随角括号包围的实参列表)时才是良构的。这种模板友元声明的模板形参必须能从该 简单模板标识 推导。

此时 A 的所有特化的成员,以及 A 的所有部分特化的所有特化的成员都会成为友元。这不并涉及对主模板 A 和 A 的部分特化的实例化:只要求从该特化推导 A 的模板形参成功,以及将推导出的模板实参替换到友元声明中所产生的声明,是该特化的成员的合法声明:

// 主模板
template<class T>
struct A
{ 
    struct B {};
 
    void f();
 
    struct D { void g(); };
 
    T h();
 
    template<T U>
    T i();
};
 
// 全特化
template<>
struct A<int>
{
    struct B {};
 
    int f();
 
    struct D { void g(); };
 
    template<int U>
    int i();
};
 
// 另一全特化
template<>
struct A<float*>
{
    int *h();
};
 
// 非模板类向类模板 A 的成员授予友元关系
class X
{
    template<class T>
    friend struct A<T>::B; // 所有的 A<T>::B 都是友元,包括 A<int>::B
 
    template<class T>
    friend void A<T>::f(); // A<int>::f() 不是友元,因为其签名不匹配,
                           // 但比如 A<char>::f() 则是友元
 
//  template<class T>
//  friend void A<T>::D::g(); // 非良构:嵌套名说明符的最后部分,
//                            // A<T>::D:: 中 的 D,不是简单模板标识
 
    template<class T>
    friend int* A<T*>::h(); // 所有的 A<T*>::h 都是友元:A<float*>::h()、A<int*>::h() 等
 
    template<class T> 
    template<T U>       // A<T>::i() 的所有实例化和 A<int>::i() 都是友元,
    friend T A<T>::i(); // 从而这些函数模板的所有特化都是
};

只有在模板友元声明是定义,且此翻译单元不出现此函数模板的其他声明时,该声明中才允许使用默认模板实参

(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;
 
    // 为这个 T 生成非模板 operator<< 
    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; // 前置声明,以确保函数声明可行
 
template<typename T> // 声明
std::ostream& operator<<(std::ostream&, const Foo<T>&);
 
template<typename T>
class Foo
{
public:
    Foo(const T& val) : data(val) {}
private:
    T data;
 
    // 涉指对于这个特定的 T 的全特化 
    friend std::ostream& operator<< <> (std::ostream&, const Foo&);
 
    // 注意:这依赖于声明中的模板实参推导
    // 也可以通过 operator<< <T> 指定模板实参
};
 
// 定义
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';
}

示例

流插入与提取运算符往往声明为非成员友元:

#include <iostream>
#include <sstream>
 
class MyClass
{
    int i;
 
    friend std::ostream& operator<<(std::ostream& out, const MyClass& o);
    friend std::istream& operator>>(std::istream& in, MyClass& o);
public:
    MyClass(int i = 0) : i(i) {}
};
 
std::ostream& operator<<(std::ostream& out, const MyClass& mc)
{
    return out << mc.i;
}
 
std::istream& operator>>(std::istream& in, MyClass& mc)
{
    return in >> mc.i;
}
 
int main()
{
    MyClass mc(7);
    std::cout << mc << '\n';
    std::istringstream("100") >> mc;
    std::cout << mc << '\n';
}

输出:

7
100

缺陷报告

下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。

缺陷报告 应用于 出版时的行为 正确行为
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 已禁止

引用

  • C++20 标准(ISO/IEC 14882:2020):
  • 11.9.3 Friends [class.friend]
  • 13.7.4 Friends [temp.friend]
  • C++17 标准(ISO/IEC 14882:2017):
  • 14.3 Friends [class.friend]
  • 17.5.4 Friends [temp.friend]
  • C++14 标准(ISO/IEC 14882:2014):
  • 11.3 Friends [class.friend]
  • 14.5.4 Friends [temp.friend]
  • C++11 标准(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]

参阅

类类型 定义保有数个数据成员的类型
访问说明符 定义类成员的可见性