我一直在阅读c++常见问题,对朋友声明很好奇。我个人从未使用过它,但我对探索这门语言很感兴趣。

使用friend的一个好例子是什么?


读了一些常见问题,我喜欢<< >>操作符重载和添加作为这些类的朋友的想法。然而,我不确定这如何不会破坏封装。什么时候这些异常可以保持在OOP的严格范围内?


当前回答

在我之前工作过的一家公司里,我们遇到了一个有趣的问题,我们用朋友来产生良好的影响。我在我们的框架部门工作,我们在自定义操作系统上创建了一个基本的引擎级系统。在内部我们有一个类结构:

         Game
        /    \
 TwoPlayer  SinglePlayer

所有这些类都是框架的一部分,由我们的团队维护。该公司所制作的游戏便是基于这一源自games子游戏的框架。问题是Game有各种单人和双人玩家需要访问的东西的接口,但我们不想在框架类之外公开。解决方案是将这些接口设为私有,并允许双人和单人玩家通过友谊访问它们。

事实上,这整个问题本可以通过更好地实施我们的系统来解决,但我们被锁定在我们所拥有的东西上。

其他回答

在工作中,我们广泛地请朋友来测试代码。这意味着我们可以为主应用程序代码提供适当的封装和信息隐藏。但我们也可以有单独的测试代码,使用朋友来检查内部状态和数据进行测试。

可以说,我不会将friend关键字作为设计的重要组成部分。

编辑:阅读faq有点长,我喜欢<< >>操作符重载和添加作为这些类的朋友的想法,但我不确定这如何不打破封装

它将如何破坏封装?

当允许对数据成员进行不受限制的访问时,就打破了封装。考虑以下类:

class c1 {
public:
  int x;
};

class c2 {
public:
  int foo();
private:
  int x;
};

class c3 {
  friend int foo();
private:
  int x;
};

C1显然没有封装。任何人都可以读取和修改其中的x。我们没有办法实施任何形式的访问控制。

C2明显被封装了。没有对x的公共访问。你所能做的就是调用foo函数,它对类执行一些有意义的操作。

c3吗?它的封装程度低吗?它是否允许无限制地访问x?它是否允许未知函数访问?

不。它只允许一个函数访问类的私有成员。就像c2一样。就像c2一样,有访问权的函数不是“某个随机的未知函数”,而是“类定义中列出的函数”。就像c2一样,通过查看类定义,我们可以看到拥有访问权限的完整列表。

那么,这到底是如何减少封装的呢?同样数量的代码可以访问类的私有成员。类定义中列出了所有具有访问权限的人。

Friend不会破坏封装。这让一些Java程序员感到不舒服,因为当他们说“面向对象”时,他们实际上指的是“Java”。当他们说“封装”时,他们并不是说“必须保护私有成员不受任意访问”,而是说“在Java类中,唯一能够访问私有成员的函数是类成员”,尽管出于几个原因,这完全是无稽之谈。

首先,如上所述,它限制太大。没有理由不允许朋友方法做同样的事情。

第二,限制不够。考虑第四个类:

class c4 {
public:
  int getx();
  void setx(int x);
private:
  int x;
};

根据上述Java思想,这是完美封装的。 但是,它允许任何人读取和修改x,这有什么意义呢?(提示:事实并非如此)

底线: 封装是关于能够控制哪些函数可以访问私有成员。这与这些函数的定义具体位于何处无关。

安德鲁例子的另一个常见版本,可怕的密码对联

parent.addChild(child);
child.setParent(parent);

与其担心这两行是否总是一起执行,并且顺序一致,你可以将方法设为私有,并有一个friend函数来强制一致性:

class Parent;

class Object {
private:
    void setParent(Parent&);

    friend void addChild(Parent& parent, Object& child);
};

class Parent : public Object {
private:
     void addChild(Object& child);

     friend void addChild(Parent& parent, Object& child);
};

void addChild(Parent& parent, Object& child) {
    if( &parent == &child ){ 
        wetPants(); 
    }
    parent.addChild(child);
    child.setParent(parent);
}

换句话说,您可以保持公共接口更小,并强制在友元函数中跨越类和对象的不变量。

对于操作符<<和操作符>>,没有很好的理由让这些操作符成为朋友。它们确实不应该是成员函数,但它们也不需要是朋友函数。

最好的方法是创建公共打印(ostream&)和读取(istream&)函数。然后,根据这些函数写出操作符<<和操作符>>。这提供了额外的好处,允许您将这些函数设置为虚拟函数,从而提供虚拟序列化。

简短的回答是:在friend确实可以改善封装时使用它。提高可读性和可用性(操作符<<和>>是典型的例子)也是一个很好的理由。

至于改进封装的例子,专门设计用于处理其他类的内部结构的类(想到了测试类)是很好的候选。