友元声明

出自cppreference.com


 
 
C++ 語言
 
 

友元聲明在類體內出現,並向一個函數或另一個類授予對包含友元聲明的類的私有及受保護成員的訪問權。

語法

friend 函數聲明 (1)
friend 函數定義 (2)
friend 詳述類型說明符 ; (3) (C++26 前)
friend 簡單類型說明符 ;

friend typename說明符 ;

(4) (C++11 起)
(C++26 前)
friend 友元類型說明符列表 ; (5) (C++26 起)
1,2) 函數友元聲明。
3-5) 類友元聲明。
函數聲明 - 函數聲明
函數定義 - 函數定義
詳述類型說明符 - 詳述類型說明符
簡單類型說明符 - 簡單類型說明符
typename說明符 - 關鍵詞 typename 後隨有限定標識符或有限定簡單模板標識
友元類型說明符列表 - 逗號分隔的包含簡單類型說明符詳述類型說明符 和typename說明符 的非空列表,每個說明符都可後隨一個省略號(...

描述

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,4) 指定一個類作為此類的友元。這意味着該友元的各成員聲明和定義可以訪問此類的私有與受保護成員,而且該友元關係也能被此類的私有或受保護成員繼承到其派生類。
3) 類以詳述類說明符(見詳述類型說明符)指名。不需要提前聲明用於此友元聲明的類名。
4) 類以簡單類型說明符 或typename說明符 指名,如果指名的不是類類型,那麼忽略該友元聲明。此聲明不會前置聲明新類型。
5) 指定友元類型說明符列表 中的所有類作為此類的友元。這意味着這些友元的各成員聲明和定義可以訪問此類的私有與受保護成員,而且這些友元關係也能被此類的私有或受保護成員繼承到其派生類。如果指名的某個類型不是類類型,那麼在該友元聲明中忽略它。
友元類型說明符列表 中的每個說明符在沒有後隨省略號的情況下指名一個類,否則適用包展開
class Y {};

class A
{
    int data; // 私有数据成员
    
    class B {}; // 私有嵌套类型
    
    enum { a = 100 }; // 私有枚举项
    
    friend class X; // 友元类的前置声明(详述类型说明符)
    friend Y; // 友元类声明(简单类型说明符)(C++11 起)
    
    // C++26 起以上两个友元声明可以合并:
    // friend class X, Y;
};

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

模板友元

函數模板類模板聲明,都可帶 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
};

當友元聲明指代函數模板的全特化時,不能使用關鍵詞 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); // 错误:不允许默认实参
};

模板友元聲明可以指名類模板 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';
}

鏈接

友元函數聲明中不允許使用存儲類說明符

如果在���元聲明中首次聲明並定義了一個函數或函數模板,並且外圍類在某條導出聲明中定義,那麼它的名字具有與外圍類的名字相同的鏈接。

(C++20 起)

否則,(C++20 起)如果在友元聲明中聲明了一個函數或函數模板,並且對應的非友元聲明可及,那麼它的名字與先前聲明的名字具有相同的鏈接。

否則,友元聲明引入的名字的鏈接與通常情況下一樣。

註解

友元關係不傳遞(你朋友的朋友不是你的朋友)。

友元關係不繼承(你朋友的孩子不是你的朋友,你的朋友不是你孩子的朋友)。

訪問說明符對於友元聲明的含義沒有影響(它們可以在 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實參依賴查找),除非在命名空間作用域中提供與之匹配的聲明——細節見命名空間

功能特性測試宏 標準 功能特性
__cpp_variadic_friend 202403L (C++26) 可變參數友元聲明

關鍵詞

friend

示例

流插入與提取運算符往往聲明為非成員友元:

#include <iostream>
#include <sstream>

class MyClass
{
    int i;                   // 友元能够访问非公开的非静态
    static inline int id{6}; // 和静态(可能内联的)成员

    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;  // 错误: i 是私有成员
    std::istringstream("100") >> mc;
    std::cout << mc << '\n';
//  MyClass::id = 222*3;  // 错误: id 是私有成员
    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 的友元類的嵌套類的成員對 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 Friends [class.friend]
  • 13.7.5 Friends [temp.friend]
  • 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]

參閱

類類型 定義保有數個數據成員的類型[編輯]
訪問說明符 定義類成員的可見性[編輯]