你什么时候应该使用朋友课?

时间:2011-07-16 15:05:06

标签: c++ oop class class-design friend

  

可能重复:
  When should you use 'friend' in C++?

由于缺乏有关朋友课程的文档,我遇到了绊脚石。大多数书籍只是简单地解释一下,例如摘自 C ++:完整参考

 Friend Classes are seldom used. They are supported to allow certain special case        
 situations to be handled.

坦率地说,我从未见过有经验的C ++程序员编写的任何优秀代码中的朋友类。所以,这是我的问题列表。

1- Do Inherited Classes与基类有相同的朋友吗?例如,如果我将类foo声明为类base的朋友,class der(派生自base)也会将foo作为朋友吗?

2-应该使用朋友类时特殊情况是什么?

3-我正在创建一个winapi包装器,我想让类WinHandle成为类Widget的一个朋友(访问一些受保护的成员)。推荐吗?或者我应该使用传统的Get / Set函数来访问它们吗?

4 个答案:

答案 0 :(得分:21)

Friend用于授予选择性访问权限,就像受保护的访问说明符一样。也很难提出使用受保护的正确用例。

一般情况下,朋友类在有意强耦合的设计中很有用:您需要在两个类之间建立特殊关系。更具体地说,一个类需要访问另一个类的内部,并且您不希望使用公共访问说明符授予每个人访问权限。

经验法则:如果公开太弱而私有太强,则需要某种形式的选定访问权限:受保护朋友(Java中的包访问说明符提供相同类型的角色)。

示例设计

例如,我曾经写过一个简单的秒表类,我希望隐藏本机秒表分辨率,但让用户通过单一方法查询已用时间,并将单位指定为某种变量(比如用户喜好选择)。而不是说elapsedTimeInSeconds()elapsedTimeInMinutes()等方法,我想要像elapsedTime(Unit::seconds)这样的东西。要实现这两个这些目标,我不能将原生解决方案公开或私有,所以我提出了以下设计。

实施概述

class StopWatch;

// Enumeration-style class.  Copy constructor and assignment operator lets
// client grab copies of the prototype instances returned by static methods.
class Unit
{
friend class StopWatch;
    double myFactor;
    Unit ( double factor ) : myFactor(factor) {}
    static const Unit native () { return Factor(1.0); }
public:
        // native resolution happens to be 1 millisecond for this implementation.
    static const Unit millisecond () { return native(); }

        // compute everything else mostly independently of the native resolution.
    static const Unit second () { return Factor(1000.0 / millisecond().myFactor); }
    static const Unit minute () { return Factor(60.0 / second().myFactor); }
};

class StopWatch
{
    NativeTimeType myStart;
    // compute delta using `NativeNow()` and cast to
    // double representing multiple of native units.
    double elapsed () const;
public:
    StopWatch () : myStart(NativeNow()) {}
    void reset () { myStart = NativeNow(); }
    double elapsed ( const Unit& unit ) const { return elapsed()*unit.myFactor; }
};

如您所见,此设计实现了两个目标:

  1. 原生分辨率永不暴露
  2. 可以存储所需的时间单位等
  3. 讨论

    我非常喜欢这种设计,因为原始实现存储了多个本机时间单位并执行了一个除法来计算经过的时间。有人抱怨分裂太慢了,我改变了Unit类来缓存红利,使elapsed()方法(一点点)更快。

    一般来说,你应该争取强大的凝聚力和弱耦合。这就是为什么朋友很少使用,建议减少类之间的耦合。但是,情况,其中强耦合提供更好的封装。在这些情况下,您可能需要friend

答案 1 :(得分:2)

Do Inherited Classes与基类有相同的朋友吗?例如,如果我将类foo声明为类库的朋友,那么class der(派生自base)也会将foo作为朋友吗?

朋友 关键字的规则是:
未继承友谊属性。
所以没有基类的朋友不会是派生类的朋友


应该使用好友类的特殊情况是什么?

坦率地说,(恕我直言)使用朋友类主要是为了实现一些易于使用的东西。如果设计软件考虑所有requirememtens,那么实际上不需要朋友类。重要的是要注意完美的设计几乎不存在,如果他们这样做,他们很难实现。

需要朋友类的示例案例:
有时可能需要测试人员类(不是发布软件的一部分)才能访问类的内部以检查和记录某些特定的结果/诊断。在这种情况下使用友元类是有意义的,以便于使用和防止设计开销。


我正在创建一个winapi包装器,我想让class WinHandle成为class Widget的朋友(访问一些受保护的成员)。推荐吗?或者我应该使用传统的Get / Set功能访问它们吗?

我会坚持使用传统的 setter / getter 。我宁愿避免使用 friend ,我可以通过常规的OOP构造工作。也许,我对使用friend非常偏执,因为如果我的类在将来改变/扩展,我会感觉到朋友的非继承属性给我带来了问题。


修改
来自@Martin的评论以及来自@André Caron的优秀答案提供了关于friend船的使用的全新视角,我以前从未遇到过这种情况。因此在上面的答案中没有说明。我将按原样留下这个答案,因为它帮助我学习了一个新的视角。希望它能帮助学习具有类似前景的人。

答案 2 :(得分:1)

friend通常会说明您通常使用单个类的位置,但您必须使用更多,因为它们具有不同的生命周期或实例计数。 friend船舶不会转让,继承或传递。

获取/设置非常糟糕,尽管比它更好。 friend允许您将伤害限制在一个级别。通常你会成为一个有中心的中间阶层。

class MyHandleIntermediary;
class MyHandle {
    friend class MyHandleIntermediary;
private:
    HANDLE GetHandle() const;
};
class MyWidget;
class MyHandleIntermediary {
    friend class MyWidget;
    static HANDLE GetHandle(const MyWidget& ref) {
        return ref.GetHandle();
    }
};
class MyWidget {
    // Can only access GetHandle() and public
};

这允许您在每个成员级别更改友谊的可访问性,并确保在单个位置记录额外的可访问性。

答案 3 :(得分:0)

我应用“友元类”的情况之一是,其中一个类由其他(对象)类组成或引用,而组合对象需要访问主类的内部。

enum MyDBTableButtonBarButtonKind {
  GoFirst,
  GoPrior,
  GoNext,
  GoLast
}

class MyDBTableButtonBarButtonWidget;

class MyDBTableButtonBarWidget {

  friend class MyDBTableButtonBarButtonWidget;

// friend access method:
protected:
  void GoFirst();
  void GoPrior();
  void GoNext();
  void GoLast();

  void DoAction(MyDBTableButtonBarButtonKind Kind);
};

class MyDBTableButtonBarButtonWidget {

private:

  MyDBTableButtonBarWidget* Toolbar;

public:

  MyDBTableButtonBarButtonWidget(MyDBTableButtonBarWidget* AButtonBar) {
    this ButtonBar = AButtonBar;
  }

  MyDBTableButtonBarButtonKind Kind; 
public:
  virtual void Click() { Buttonbar->DoAction(this Kind); }; 
};

void MyDBTableButtonBarWidget::DoAction(MyDBTableButtonBarButtonKind Kind)
{
  switch (Kind)
  {
     case MyDBTableButtonBarButtonKind::GoFirst:
       this.GoFirst();
     break;

     case MyDBTableButtonBarButtonKind::GoLast:
       this.GoLast();
     break;
  }
}

在此示例中,有一个窗口小部件控件,它是一个由按钮组成的栏,按钮栏以D.B.为参考。表,并有几个按钮用于特定操作, 比如选择表格的第一条记录,编辑,移动到下一条记录。

为了做到这一点,每个按钮类都有朋友访问私人&给定控制的受保护方法。正如本文前面的回答一样,通常朋友类只运行一个类,在几个较小的类中进行分解。

这不是一个完整的例子,它只是一个普遍的想法。