友元 (friends)

友元

什么是 friend

允许你的类授予访问权限给另一个类或函数的机制。

友元可以是函数,也可以是其他类。一个类将其访问权限授予给它的友元。通常,开发人员对一个类的 friend 和成员函数都拥有政治和技术控制(否则,当你想要更新自己的类时,可能需要获得其他部分所有者的许可)。

友元是否违反了封装性?

不!如果使用得当,它们会**增强**封装性。

“友元”是一种明确授予访问权限的机制,就像成员资格一样。在符合标准的程序中,你不能在不修改其源代码的情况下,自行获得对某个类的访问权限。例如:

    class X {
        int i;
    public:
        void m();       // grant X::m() access
        friend void f(X&);  // grant f(X&) access
        // ...
    };

    void X::m() { i++; /* X::m() can access X::i */ }

    void f(X& x) { x.i++; /* f(X&) can access X::i */ }

有关 C++ 保护模型的描述,请参阅 D&E 第 2.10 节和 TC++PL 第 11.5 节、15.3 节和 C.11 节。

当一个类被分成两半,并且这两半将具有不同数量的实例或不同的生命周期时,你经常需要这样做。在这些情况下,这两半通常需要直接互相访问(这两半**曾经**在同一个类中,所以你并没有**增加**需要直接访问数据结构的代码量;你只是简单地将代码**重组**成两个类而不是一个)。实现此目的最安全的方法是使这两半互为友元。

如果你像刚才描述的那样使用友元,你将使 private 的东西保持 private。不理解这一点的人常常天真地试图避免在上述情况下使用友元,而他们往往实际上破坏了封装。他们要么使用 public 数据(这很糟糕!),要么通过 publicget()set() 成员函数在两半之间访问数据。为 private 数据提供 publicget()set() 成员函数,只有当 private 数据从类外部(从用户的角度)“有意义”时才合适。在许多情况下,这些 get()/set() 成员函数几乎和 public 数据一样糟糕:它们只是隐藏了 private 数据的**名称**,但它们没有隐藏 private 数据的存在。

同样,如果你将友元函数用作类的 public 访问函数的语法变体,它们对封装的违反程度不会超过成员函数对封装的违反。换句话说,一个类的友元不会违反封装边界:它们与类的成员函数一起,**构成**了封装边界。

(许多人认为友元函数是类外部的东西。相反,试着将友元函数看作类公共接口的一部分。类声明中的友元函数对封装的违反程度不比公共成员函数对封装的违反程度大:两者在访问类的非公共部分方面拥有完全相同的权限。)

使用 friend 函数有哪些优点/缺点?

它们为接口设计选项提供了自由度。

成员函数和 friend 函数具有同等特权(100% 授予)。主要区别在于 friend 函数像 f(x) 一样调用,而成员函数像 x.f() 一样调用。因此,在成员函数 (x.f()) 和 friend 函数 (f(x)) 之间进行选择的能力允许设计者选择被认为最可读的语法,从而降低维护成本。

friend 函数的主要缺点是,当您需要动态绑定时,它们需要额外的一行代码。为了获得 virtual friend 的效果,friend 函数应该调用一个隐藏的(通常是 protectedvirtual 成员函数。这被称为虚拟友元函数习语。例如:

class Base {
public:
  friend void f(Base& b);
  // ...
protected:
  virtual void do_f();
  // ...
};

inline void f(Base& b)
{
  b.do_f();
}

class Derived : public Base {
public:
  // ...
protected:
  virtual void do_f();  // "Override" the behavior of f(Base& b)
  // ...
};

void userCode(Base& b)
{
  f(b);
}

userCode(Base&) 中的语句 f(b) 将调用 b.do_f(),它是 virtual 的。这意味着如果 b 实际上是 class Derived 的对象,则 Derived::do_f() 将获得控制权。请注意,Derived 覆盖了 protected virtual 成员函数 do_f() 的行为;它**没有**自己的 friend 函数 f(Base&) 的变体。

“友元关系不能继承、不具有传递性、不具有互惠性”是什么意思?

仅仅因为我授予你友元访问权,并不意味着你的孩子自动拥有对我的访问权,不意味着你的朋友自动拥有对我的访问权,也不意味着我自动拥有对你的访问权。

  • 我不一定信任我的朋友的孩子。友元特权不会被继承。友元的派生类不一定是友元。如果 class Fred 声明 class Basefriend,那么从 Base 派生的类不会自动拥有对 Fred 对象的任何特殊访问权限。
  • 我并不一定信任我的朋友的朋友。友元的特权不具有传递性。朋友的朋友不一定是朋友。如果 class Fred 声明 class Wilma 为友元,并且 class Wilma 声明 class Betty 为友元,那么 class Betty 不一定拥有对 Fred 对象的任何特殊访问权限。
  • 仅仅因为我声明你是我的朋友,你并不一定会信任我。友谊的特权不是互惠的。如果类 Fred 声明类 Wilma 是友元,那么 Wilma 对象对 Fred 对象具有特殊访问权限,但 Fred 对象不会自动对 Wilma 对象具有特殊访问权限。

我的类应该声明一个成员函数还是一个 friend 函数?

能用成员函数时就用成员函数,必须用 friend 时才用 friend

有时友元在语法上更好(例如,在 class Fred 中,友元函数允许 Fred 参数排在第二位,而成员函数要求它排在第一位)。友元函数的另一个很好的用途是二元中缀算术运算符。例如,如果你还想允许 aFloat + aComplex,那么 aComplex + aComplex 应该定义为友元而不是成员函数(成员函数不允许左手参数提升,因为那会改变作为成员函数调用接收者的对象的类)。

在其他情况下,请选择成员函数而不是 friend 函数。