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

5
在(其他方面)优秀的书籍C++编程规范中,第44项标题为“优先编写非成员非友元函数”,Sutter和Alexandrescu建议只有真正需要访问类成员的函数才是该类的成员。所有其他可以仅使用成员函数编写的操作都不应该是类的一部分。它们应该是非成员和非友元。其论点是:
  • 它促进了封装,因为需要访问类内部的代码更少。
  • 它使编写函数模板更容易,因为您不必每次猜测某个函数是否是成员。
  • 它使类保持小巧,从而更易于测试和维护。

虽然我看到这些论点的价值,但我看到一个巨大的缺点:我的IDE无法帮助我找到这些函数!每当我有某种对象,并且我想查看它上面可用的操作时,我不能只输入“pMysteriousObject->”并获得成员函数列表。

保持简洁的设计最终是为了让你的编程生活更轻松。但这实际上会使我的生活更加艰难。 所以我在想,这真的值得吗?你怎么处理这个问题?

好问题,我认为。虽然老,但很好。 - Sebastian Mach
7个回答

5

Scott Meyers与Sutter有相似的观点,详情请见这里

他还明确表示:

"基于他对各种类似字符串的类的工作,Jack Reeves观察到,即使它们可以是非友元非成员函数,某些函数在作为非成员函数时感觉不太对。一个类的“最佳”接口只能通过平衡许多竞争关注点来找到,其中封装程度只是其中之一。"

如果一个函数在作为成员函数时“很自然”,那么就将其定义为成员函数。同样,如果它并不真正是主接口的一部分,并且在作为非成员函数时“很自然”,那么就将其定义为非成员函数。

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


5
我认为毫不夸张地说,在C++语言的质量方面,Sutter、Alexandrescu和Meyers三位专家的贡献是最大的。
他们提出了一个简单的问题:
如果一个工具函数有两个独立的类作为参数,哪个类应该“拥有”成员函数?
另一个问题是,你只能在你控制的类中添加成员函数。任何你为std::string编写的辅助函数都必须是非成员函数,因为你不能重新打开类定义。
对于这两个例子,你的IDE将提供不完整的信息,你将不得不使用“老式方法”。
鉴于世界上最有影响力的C++专家认为,带有类参数的非成员函数是类接口的一部分,这更多地是与你的IDE有关,而不是编码风格的问题。
你的IDE很可能会在未来的版本中进行更改,甚至你可能能够让它们添加这个功能。如果你改变你的编码风格以适应今天的IDE,你可能会发现在未来会有更大的问题,比如无法扩展/难以维护的代码。

3
我对Sutter和Alexandrescu的观点持不同意见。如果函数foo()的行为属于类Bar的职责范畴,则foo()应该是bar()的一部分。即使foo()不需要直接访问Bar的成员数据,也并不意味着它不是Bar的概念部分。这也可能意味着代码被很好地分解了。成员函数通过其他成员函数执行其所有行为并不罕见,我不明白为什么会有问题。
我完全同意,与类周边相关的函数不应该是类的一部分,但是如果某些内容是类职责的核心,则没有理由不将其作为成员,无论是否直接涉及成员数据。
至于这些具体的观点:
“它促进了封装,因为需要访问类的内部的代码更少。”
确实,直接访问内部的函数越少越好。这意味着,让成员函数尽可能通过其他成员函数完成工作是一件好事。将良好分解的函数从类中拆分出来只会留下半个类,需要大量外部函数才能使用。将良好分解的函数从它们的类中拆分出来似乎也会阻碍编写良好分解的函数。
“它使编写函数模板更容易,因为您不必每次都猜测某个函数是否是成员。”
我完全不理解这一点。如果您将一堆函数从类中拆分出来,则会向函数模板投入更多的责任。它们被迫假设其类模板参数提供的功能更少,除非我们假设大多数从类中提取的函数将转换为模板(呕吐)。
“它使类保持小巧,从而更容易进行测试和维护。”
嗯,当然。这也会创建许多额外的外部函数以进行测试和维护。我看不出其中的价值。

4
请注意,C++认为这些自由函数仍然是类的接口的一部分——但我想这是一个开放的命名问题,可以进行另外的辩论。 - Carl Seleborg
这不是一个命名问题,而是接口原则。C++规定函数是其第一个参数接口的一部分。更重要的是,如果它们在与第一个参数相同的命名空间中定义,则被认为具有与类方法相同的优先级。 - Vincent Robert
1
Carl,C ++可以随便怎么认为。一旦它们不再是成员函数,程序员 就不会认为它们是类的一部分。我更关心程序员如何使用类,而不是语言的细节。 - Derek Park

1

确实,外部函数不应该成为接口的一部分。理论上,您的类应该只包含数据并公开其预期的接口,而不是实用函数。将实用函数添加到接口中只会增加类代码库并使其难以维护。我目前维护一个具有约50个公共方法的类,这简直是疯狂。

现实情况下,我同意这很难强制执行。通常更容易只需向类添加另一个方法,尤其是如果您正在使用可以轻松向现有类添加新方法的IDE。

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

例如:创建一个Point类,然后添加一个PointDrawer类来将其绘制到位图中,PointSerializer类来保存它等等。


0
在许多面向对象的编程语言中,非友元非类方法是居住在孤儿院中,与任何东西都没有联系的第三等公民。当我写一个方法时,我喜欢选择好的父母 - 适合的类 - 在那里他们有最好的机会感到受欢迎并提供帮助。

0
如果你给它们一个共同的前缀,那么当你输入时,你的IDE可能会提供帮助。
::prefix

或者

namespace::prefix

好的,但这使得编写接受这些类作为参数的通用函数变得不可能。 - Carl Seleborg

-1

我本以为IDE实际上会帮助你。

IDE从列表中隐藏了受保护的函数,因为它们对公众不可用,正如类的设计者所期望的那样。

如果您在类的范围内并键入this->,则受保护的函数将显示在列表中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接