名稱空間
變體
操作

友元宣告

來自 cppreference.com
< 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]

[編輯] 另請參閱

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