命名空间
变体
操作

友元声明

来自 cppreference.cn
< cpp‎ | 语言
 
 
C++ 语言
 
 

友元声明出现在类体中,并授予函数或其他类访问该类(其中友元声明出现)的私有和保护成员的权限。

目录

[编辑] 语法

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列表,每个说明符后面可以跟省略号(...

[编辑] 描述

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) (仅允许在非局部类定义中使用)定义一个非成员函数,并同时使其成为此类的友元。这样的非成员函数总是inline,除非它附加到命名模块(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
};

[编辑] 模板友元

函数模板类模板声明都可以带有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 命名了类模板时,此类声明才是格式良好的。此类模板友元声明的模板参数必须可以从简单模板 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的依赖于参数的查找)是不可见的——详见命名空间

功能测试宏 标准 特性
__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++ 标准。

缺陷报告 应用于 发布时的行为 正确的行为
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]

[编辑] 另请参阅

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