什么时候应该使用好友类?

发布于 2024-11-24 13:40:32 字数 747 浏览 0 评论 0原文

可能的重复:
什么时候应该在 C++ 中使用“friend”?

什么时候应该 由于缺乏有关友元类的文档,这是一个绊脚石。大多数书籍只是简单地解释它,例如C++:完整参考的摘录:

Friend 类很少被使用。支持它们以允许处理某些特殊情况。

坦率地说,我从未在经验丰富的 C++ 程序员编写的任何优秀代码中见过友元类。所以,这是我的问题清单。

  1. 继承类是否与基类具有相同的朋友?例如,如果我将类 foo 声明为基类的友元,则类 der(从基类派生)也会将 foo 作为友元吗?

  2. 什么特殊情况应该使用友元类?

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

Possible Duplicate:
When should you use 'friend' in C++?

I have come to a stumbling block because of lack of documentation on friend classes. Most books just explain it briefly, e.g an excerpt from C++: the Complete Reference:

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

And frankly, I have never seen a friend class in any good code made by an experienced C++ programmer. So , here is my list of problems.

  1. Do Inherited Classes have the same friends as there base classes? e.g, if I declare class foo as a friend of class base, will class der (derived from base) also have foo as a friend?

  2. What are the special case situations when a friend class should be used?

  3. I am making a winapi wrapper in which I want to make class WinHandle a friend of class Widget (to access some protected members). Is it recommended? Or should I just access them using the traditional Get/Set functions?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

傲世九天 2024-12-01 13:40:32

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

一般来说,友元类在存在故意强耦合的设计中非常有用:您需要在两个类之间具有特殊关系。更具体地说,一个类需要访问另一个类的内部结构,并且您不希望使用公共访问说明符向每个人授予访问权限。

经验法则:如果公共太弱而私有太强,则需要某种形式的选定访问权限:受保护 >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 Unit(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 Unit(1000.0 / millisecond().myFactor); }
    static const Unit minute () { return Unit(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. 可以存储所需的时间单位等。

讨论

我真的很喜欢这个设计,因为原始实现存储了本机时间单位的倍数并执行除法来计算经过的时间。在有人抱怨除法太慢之后,我更改了 Unit 类来缓存被除数,使 elapsed() 方法(稍微)更快。

一般来说,应该力求强内聚、弱耦合。这就是为什么friend这么少用的原因,建议减少类之间的耦合。然而,在某些情况下,强耦合可以提供更好的封装。在这些情况下,您可能需要一个朋友

Friend is used for granting selective access, just like the protected access specifier. It's also hard to come up with proper use case where use of protected is really useful.

In general, friend classes are useful in designs where there is intentional strong coupling: you need to have a special relationship between two classes. More specifically, one class needs access to another classes's internals and you don't want to grant access to everyone by using the public access specifier.

The rule of thumb: If public is too weak and private is too strong, you need some form of selected access: either protected or friend (the package access specifier in Java serves the same kind of role).

Example design

For instance, I once wrote a simple stopwatch class where I wanted to have the native stopwatch resolution to be hidden, yet to let the user query the elapsed time with a single method and the units to be specified as some sort of variable (to be selected by user preferences, say). Rather than, have say elapsedTimeInSeconds(), elapsedTimeInMinutes(), etc. methods, I wanted to have something like elapsedTime(Unit::seconds). To achive both of these goals, I can't make the native resolution public nor private, so I came up with the following design.

Implementation overview

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 Unit(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 Unit(1000.0 / millisecond().myFactor); }
    static const Unit minute () { return Unit(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; }
};

As you can see, this design achieves both goals:

  1. native resolution is never exposed
  2. desired time unit can be stored, etc.

Discussion

I really like this design because the original implementation stored the multiple of native time units and performed a division to compute the elapsed time. After someone complained the division was too slow, I changed the Unit class to cache the dividend, making the elapsed() method (a little) faster.

In general, you should strive for strong cohesion and weak coupling. This is why friend is so little used, it is recommended to reduce coupling between classes. However, there are situations where strong coupling gives better encapsulation. In those cases, you probably need a friend.

橘亓 2024-12-01 13:40:32

继承类与基类有相同的朋友吗?例如,如果我将 foo 类声明为基类的友元,则 der 类(从基类派生)也会将 foo 作为友元吗?

friend 的规则 关键字是:
友谊属性不会继承。
因此,基类的朋友不会是派生类的朋友


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

坦率地说,(恕我直言)使用友元类主要是为了实现一些相当易于使用的事情。如果一个软件的设计考虑到了所有的要求,那么实际上就不需要friend类了。值得注意的是,完美的设计几乎不存在,即使存在,也很难实现。

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


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

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


编辑:
来自 @Martin 的评论以及来自 @André Caron 的精彩回答,提供了关于使用friendship 的全新视角,我以前没有遇到过&因此在上面的答案中没有考虑到。我将保留这个答案,因为它帮助我学习了新的观点和观点。希望它能帮助有类似观点的人们学习。

Do Inherited Classes have the same friends as there base classes? e.g if i declare class foo as a friend of class base, will class der (derived from base) also have foo as a friend?

The rule with friend keyword is:
Friendship attribute is not inherited.
So No friend of base class will not be friend of Derived class.


What are the special case situations when a friend class should be used?

Frankly, (IMHO) using friend classes is mostly done to achieve some things for rather ease of usage. If a software is designed taking in to consideration all the requirememtens then there would practically no need of friend classes. Important to note perfect designs hardly exist and if they do they are very difficult to acheive.

An example case which needs friend class:
Sometimes there may be a need for a tester class(which is not part of the release software) to have access to internals of classes to examine and log certain specific results/diagnostics. It makes sense to use friend class in such a scenario for ease of usage and preventing overhead of design.


I am making a winapi wrapper in which I want to make class WinHandle a friend of class Widget (to access some protected members). Is it recommended? Or should I just access them using the traditional Get/Set functions?

I would stick to the traditional setter/getter. I rather avoid using friend where I can get working through usual OOP construct. Perhaps, I am rather paranoid about using friend because if my classes change/expand in future I perceive the non inheritance attribute of friend causing me problems.


EDIT:
The comments from @Martin, and the excellent answer from @André Caron, provide a whole new perspective about usage of friendship, that I had not encountered before & hence not accounted for in the answer above. I am going to leave this answer as is, because it helped me learn a new perspective & hopefully it will help learn folks with a similar outlook.

屌丝范 2024-12-01 13:40:32

friend 通常说明您通常会使用一个类,但您必须使用多个类,因为它们具有不同的生命周期或实例计数等。 友谊关系不能转移、继承或传递。

Get/Set 非常糟糕,尽管比它可能的要好。 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
};

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

friend usually accounts for where you would normally use one single class, but you have to use more because they have different life-times or instance counts, for example. friendship is not transferred or inherited or transitive.

Get/Set is quite terrible, although better than it could be. friend allows you to limit the damage to just one class. Usually you will make an intermediary class which is friended.

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
};

This allows you to change the accessibility of friendship on a per-member level and ensures that the extra accessibility is documented in a single place.

萌梦深 2024-12-01 13:40:32

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

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;
  }
}

在这个例子中,有一个小部件控件,它是一个由按钮组成的栏,按钮栏引用了一个数据库表,并且有几个用于特定操作的按钮,
就像选择表的第一条记录,编辑,移动到下一条记录。

为此,每个按钮类都具有对 private 和 private 的好友访问权限。给定控件的受保护方法。正如本文之前的回答,通常友元类充当单个类,分解为几个较小的类。

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

One of the cases where I applied the "friend classes", is where one class is composed or referenced by other (objects) classes, and the composing objects, require access to the internals of the main class.

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;
  }
}

In this example, there is a widget control that is a bar composed of buttons, the buttonbar is referenced to a D.B. table, and has several buttons for a specific action,
like select the first record of the table, edit, move to the next.

In order to do so, each button class has friend access to private & protected methods, of the given control. As previous answer in this post, usually friend classes act a single class, decomposed in several smaller classes.

It's not a finished example, its only a general idea.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文