类设计与IDE:非成员非朋友函数真的值得吗?

时间:2020-03-06 14:44:43  来源:igfitidea点击:

Sutter和Alexandrescu在(否则)出色的C ++编码标准书,第44项,标题为"更喜欢编写非成员非朋友函数"中,建议只有真正需要访问类成员的函数才能成为该类的成员。仅可以使用成员函数编写的所有其他操作不应属于该类。他们应该是非会员和非朋友。参数是:

  • 它促进了封装,因为更少的代码需要访问类的内部。
  • 它使编写函数模板更加容易,因为我们不必每次都猜测某个函数是否是成员。
  • 它使类变小,从而使测试和维护变得更容易。

尽管我看到了这些参数中的值,但是我看到了一个巨大的缺点:我的IDE无法帮助我找到这些功能!每当我有某种对象时,我想看看可以使用什么操作,就不能只键入"pMysteriousObject->"并获得成员函数列表。

保持整洁的设计最终将使编程工作变得更轻松。但这实际上会使我的工作更加困难。

因此,我想知道是否值得这样做。你怎么处理?

解决方案

在这一点上,我将不得不与Sutter和Alexandrescu意见相左。我认为,如果函数foo()的行为属于类Bar的职责范围之内,则foo()应该属于bar()的一部分。

foo()不需要直接访问Bar的成员数据这一事实并不意味着它从概念上讲不是Bar的一部分。这也可能意味着代码是充分考虑因素的。具有通过其他成员函数执行其所有行为的成员函数并不少见,我不明白为什么要这样做。

我完全同意,与外围设备相关的功能不应成为类的一部分,但是,如果某些内容是类职责的核心,则没有理由不应该将其作为成员,无论它是否直接与成员数据混为一谈。

至于这些具体要点:

It promotes encapsulation, because there is less code that needs access to the internals of a class.

实际上,直接访问内部函数的功能越少越好。这意味着让成员函数通过其他成员函数尽可能多地完成工作是一件好事。从类中分解出构造良好的函数只会使我们剩下一半的类,这需要大量的外部函数才有用。将构造良好的函数从其类中移开也似乎不鼓励编写构造良好的函数。

It makes writing function templates easier, because you don't have to guess each time whether some function is a member or not.

我一点都不明白。如果我们从类中拉出一堆函数,则将更多的责任推给了函数模板。他们被迫假定他们的类模板参数提供的功能甚至更少,除非我们要假定从他们的类中提取的大多数函数都将被转换为模板(ugh)。

It keeps the class small, which in turn makes it easier to test and maintain.

嗯,当然。它还创建了许多其他外部功能来进行测试和维护。我看不到其中的价值。

如果给他们一个通用的前缀,那么如果我们键入以下内容,也许IDE会有所帮助

::prefix

或者

namespace::prefix

的确,外部函数不应成为接口的一部分。从理论上讲,类应仅包含数据并公开其预期用途的接口,而不应提供功利性函数。在界面上添加实用程序功能只会增加类代码库的数量,并使它的可维护性降低。我目前维护着一个带有约50种公共方法的类,这简直太疯狂了。

现在,实际上,我同意这并不容易执行。仅向类中添加另一个方法通常会更容易,如果我们使用的IDE确实可以简单地向现有类中添加新方法,则更容易。

为了使我的类保持简单并仍然能够集中外部函数,我经常使用与我的类甚至命名空间一起使用的实用程序类。
我首先创建将包装数据并公开最简单的接口的类。然后,我为与该班级有关的每个任务创建一个新班级。

示例:创建一个Point类,然后添加一个PointDrawer类以将其绘制到位图上,添加PointSerializer以将其保存,依此类推。

在许多OOP语言中,非朋友非类方法是居住在孤儿院中且未与任何事物连接的三等公民。当我编写一种方法时,我喜欢挑选适合的父母参加适合的班级,使他们有最大的机会受到欢迎和帮助。

我本以为IDE确实可以。

IDE将从列表中隐藏受保护的函数,因为它们不能像预期的类的设计者那样对公众使用。

如果我们属于该类的范围并键入this->,则受保护的函数将显示在列表中。

斯科特·迈耶斯(Scott Meyers)与萨特(Sutter)有类似的看法,请参见此处。

他还明确指出以下内容:

"基于他在各种类似于字符串的类上的工作,Hyman·里夫斯观察到,有些函数在成为非成员时,即使它们可能是非朋友的非成员,也不会"感到"。"最佳"接口只能通过平衡许多相互竞争的问题来找到一个类,其中封装的程度只是一个。"

如果某个函数成为某个"合理"的成员函数,则使其成为一个成员函数。同样,如果它实际上不是主界面的一部分,并且成为非成员"很有意义",则可以这样做。

需要注意的是,对于重载版本的operator ==(),语法保持不变。因此,在这种情况下,除非它确实需要访问私有成员(根据我的经验,很少会这样做),否则我们没有理由不使它成为与类在同一位置声明的非成员非朋友浮动函数。即使这样,我们也可以将operator!=()定义为非成员,并根据operator ==()进行定义。

我认为说Sutter,Alexandrescu和Meyers在他们之间为C ++所做的工作比其他任何人都做得要好。

他们提出的一个简单问题是:

If a utility function has two independent classes as parameteres, which class should "own" the member function?

另一个问题是,我们只能在相关类处于控制之下的情况下添加成员函数。我们为std :: string编写的任何辅助函数都必须是非成员的,因为我们无法重新打开类定义。

对于这两个示例,IDE将提供不完整的信息,并且我们将不得不使用"旧的方式"。

鉴于世界上最有影响力的C ++专家认为带有类参数的非成员函数是类接口的一部分,所以这更多是与IDE有关的问题,而不是编码样式。

IDE可能会在一个或者两个版本中进行更改,我们甚至可以让他们添加此功能。如果我们更改编码样式以适合当今的IDE,则很可能会发现将来使用不可扩展/不可维护的代码会遇到更大的问题。