友元声明

来自cppreference.com
< cpp‎ | language

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

目录

[编辑] 语法

friend function-declaration (1)
friend function-definition (2)
friend elaborated-class-specifier ; (3)
friend simple-type-specifier ;

friend typename-specifier ;

(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) (只允许在非局部类定义中)定义一个非成员函数,同时令之为此类的友元。这种非成员函数始终为 inline
class X {
    int a;
    friend void friend_set(X& p, int i) {
        p.a = i; // 此为非成员函数
    }
 public:
    void member_set(int i) {
        a = i; // 此为成员函数
    }
};
3) 指明以 elaborated-class-specifier (见详细类型指定符)命名的 class 、 struct 或 union 为此类的友元。这表示该友元的成员声明能访问此类的私有与受保护成员,而且该友元能继承自此类的私有或受保护成员。 若用于友元声明的类名仍未声明,则它在该点被前置声明。
4) 指明以 simple-type-specifiertypename-specifier 命名的类型为此类的友元,若该类型是(可有 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 和默认参数。

template<class T> void f(int);
template<> void f<int>(int);
 
class X {
    friend void f<int>(int x = 1); // 错误:不允许默认参数
};

若类模板 A 的成员被声明为非模板类 B 的友元,则 A 每个特化的对应成员都成为 B 的友元。若 A 被显式特化,则只要有同名、同类型(类型、函数、类模板、函数模板)、同参数/签名的成员,就成为 B 的友元。

template<typename T> // 基础模板
struct A
{
    struct C {};
    void f();
    struct D {
        void g();
    };
};
 
template<> // 全特化
struct A<int>
{
    struct C {};
    int f();
    struct D {
        void g();
    };
};
 
class B // 非模板类
{
    template<class T>
    friend struct A<T>::C; // A<int>::C 是友元,和所有 A<T>::C 一样
 
    template<class T>
    friend void A<T>::f(); // A<int>::f() 不是友元,因为签名不匹配
                           // 但 A<char>::f() 是
 
    template<class T>
    friend void A<T>::D::g(); // A<int>::D::g() 不是友元:它不是 A 的成员
                              // 且 A<int>::D 不是 A<T>::D 的特化
};

默认模板实参仅在模板友元声明允许,若声明是定义且此翻译单元不出现此函数模板的其他声明。

(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> 内的友元声明能对其 T 指代 operator<< 的全特化:

#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++ 标准。

DR 应用于 出版时的行为 正确行为
CWG 45 C++98 友元的嵌套类的成员没有访问特权 嵌套类拥有和外围类相同的访问权
CWG 500 C++98 T 的友元不能继承自 T 的私有与受保护成员,但友元的嵌套类可以 友元和友元的嵌套类都可以继承自此种成员

[编辑] 引用

  • C++11 standard (ISO/IEC 14882:2011):
  • 11.3 Friends [class.friend]
  • 14.5.4 Friends [temp.friend]
  • C++98 standard (ISO/IEC 14882:1998):
  • 11.3 Friends [class.friend]
  • 14.5.3 Friends [temp.friend]

[编辑] 参阅

类声明
访问指定符