我什么时候应该更喜欢非成员非友元函数而不是成员函数

When should I prefer non-member non-friend functions to member functions?

本文关键字:函数 成员 什么时候 友元 更喜欢      更新时间:2023-10-16

Meyers在他的《Effective C++》一书中提到,在某些情况下,非成员非友元函数比成员函数更好地封装。

例:

// Web browser allows to clear something
class WebBrowser {
public:
  ...
  void clearCache();
  void clearHistory();
  void removeCookies();
  ...
};

许多用户希望同时执行所有这些操作,因此WebBrowser可能还会提供一个功能来执行此操作:

class WebBrowser {
public:
  ...
  void clearEverything();  // calls clearCache, clearHistory, removeCookies
  ...
};

另一种方法是定义一个非成员非友元函数。

void clearBrowser(WebBrowser& wb)
{
  wb.clearCache();
  wb.clearHistory();
  wb.removeCookies();
}

非成员函数更好,因为"它不会增加可以访问类私有部分的函数数量",从而导致更好的封装。

clearBrowser这样的函数是方便函数,因为它们不能提供WebBrowser客户端无法以其他方式获得的任何功能。例如,如果clearBrowser不存在,客户端可以只调用clearCacheclearHistory和自己removeCookies

对我来说,便利功能的例子是合理的。但是,当非会员版本表现出色时,除了便利功能之外,还有其他例子吗?

更一般地说,何时使用哪个的规则是什么

更一般地说,何时使用哪个的规则是什么?

以下是斯科特·迈耶的规则(来源(:

斯科特有一篇有趣的文章,倡导 非成员非友元函数改进了封装 对于类。他使用以下算法来确定 函数 f 的位置:

if (f needs to be virtual)
    make f a member function of C;
else if (f is operator>> or operator<<)
{
   make f a non-member function;
   if (f needs access to non-public members of C)
      make f a friend of C;
}
else if (f needs type conversions on its left-most argument)
{
   make f a non-member function;
   if (f needs access to non-public members of C)
      make f a friend of C;
}
else if (f can be implemented via C's public interface)
   make f a non-member function;
else
   make f a member function of C;

他对封装的定义涉及数字 私有数据时受影响的功能 成员已更改。

这几乎总结了这一切,在我看来,这也是相当合理的。

我经常选择在我的类之外构建特定于应用程序的实用程序方法。

应用程序通常处于不同的上下文中,然后引擎在下面执行工作。如果我们以 Web 浏览器为例,3 个 clear 方法属于 Web 引擎,因为这是在其他任何地方都难以实现的必需功能,但是,ClearEverything(( 肯定更特定于应用程序。在这种情况下,应用程序可能有一个小对话框,其中包含一个清除"全部"按钮,以帮助用户提高效率。也许这不是另一个重用您的 Web 浏览器引擎的应用程序想要做的事情,因此将其放在引擎类中只会更加混乱。

另一个例子是数学库中的。通常,将更高级的功能(如平均值或标准推导(作为数学类的一部分实现是有意义的。但是,如果您有特定于应用程序的方法来计算某种类型的均值,但该方法不是标准版本,则它可能应该在您的类之外,并且是特定于您的应用程序的实用程序库的一部分。

我从来都不是强硬编码规则的忠实粉丝,以这种或那种方式实现事物,这通常是意识形态和原则的问题。

米.

当库的开发人员想要编写二进制运算符时,通常使用非成员函数,这些运算符可以在具有类类型的任一参数上重载,因为如果将它们设置为类的成员,则只能在第二个参数上重载(第一个参数隐式是该类的对象(。complex的各种算术运算符可能是这方面的决定性例子。

在你引用的例子中,动机是另一种:使用耦合最少的设计,仍然允许你完成工作

这意味着,虽然clearEverything可以(坦率地说,很有可能(成为会员,但我们没有使其成为会员,因为从技术上讲,它不必成为会员。这给你买了两样东西:

  1. 您不必承担在公共界面中拥有clearEverything方法的责任(一旦您附带了一个方法,您就与它终身结婚(。
  2. 可以访问类private成员的函数数量要少一个,因此将来的任何更改都将更容易执行,并且不太可能导致错误。

也就是说,在这个特殊的例子中,我觉得这个概念走得太远了,对于这样一个"无辜"的功能,我倾向于让它成为成员。但这个概念是合理的,在"现实世界"场景中,事情不是那么简单,它会更有意义。

局部性和允许类在保持封装的同时提供"足够"的功能是需要考虑的一些事项。

如果WebBrowser在许多地方重用,依赖项/客户端可能会定义多个方便的功能。这使您的类(WebBrowser(保持轻量级且易于管理。

相反,WebBrowser最终取悦了所有客户,只是变成了一些难以改变的铁板一块野兽。

您是否发现该类在多个场景中使用后缺乏功能?您的便利功能中会出现模式吗?最好 (IMO( 推迟正式扩展类的接口,直到模式出现并且有充分的理由添加此功能。最小类更易于维护,但您不希望到处都是冗余实现,因为这会将维护负担推给客户端。

如果您的便利函数实现起来很复杂,或者存在可以显着提高性能的常见情况(例如,使用一个锁清空线程安全集合,而不是每次使用一个锁一次清空一个元素(,那么您可能还需要考虑这种情况。

在某些情况下,当您使用它时,您会意识到WebBrowser中确实缺少某些东西。