在这本(否则)优秀的书中 C++ 编码标准, ,第 44 项,标题为 “更喜欢编写非成员非友元函数”, Sutter 和 Alexandrescu 建议,只有真正需要访问类成员的函数本身才是该类的成员。只能使用成员函数编写的所有其他操作不应成为类的一部分。他们应该是非会员和非朋友。论据是:

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

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

保持简洁的设计最终是为了让你的编程生活更轻松。但这实际上会让我的工作变得更加困难。

所以我想知道是否真的值得这么麻烦。 你怎么处理那件事呢?

有帮助吗?

解决方案

在这一点上我将不得不不同意萨特和亚历山德雷斯库的观点。我认为如果函数的行为 foo() 属于阶级范畴 Bar的责任,那么 foo() 应该是一部分 bar().

事实是 foo() 不需要直接访问 Bar的成员数据并不意味着它在概念上不是 Bar. 。这也可能意味着代码经过精心分解。通过其他成员函数执行其所有行为的成员函数并不罕见,但我不明白为什么应该这样。

我完全同意外围相关功能应该 不是 是类的一部分,但如果某些东西是类职责的核心,那么它就没有理由不应该成为成员,无论它是否直接与成员数据混在一起。

至于具体的几点:

它促进了封装,因为需要访问类内部的代码更少。

事实上,直接访问内部的函数越少越好。这意味着让成员函数做尽可能多的事情 通过 其他成员函数是一件好事。将分解良好的函数从类中分离出来只会留下半个类,这需要一堆有用的外部函数。将分解良好的函数从类中取出似乎也会阻碍分解分解良好的函数的编写。

它使编写函数模板变得更容易,因为您不必每次都猜测某个函数是否是成员。

我完全不明白这一点。如果您从类中取出一堆函数,那么您就将更多的责任推给了函数模板。他们被迫假设 甚至更少 功能由它们的类模板参数提供,除非我们假设从它们的类中提取的大多数函数将被转换为模板(呃)。

它使类保持较小,从而更容易测试和维护。

嗯,当然。它还创建了许多额外的外部函数来测试和维护。我看不到这其中的价值。

其他提示

斯科特·迈耶斯 (Scott Meyers) 与萨特 (Sutter) 的观点相似,请参阅 这里.

他还明确指出:

“根据他对各种类似字符串的类的研究,Jack Reeves 观察到,某些函数在成为非成员时“感觉”不太好,即使它们可能是非友元非成员。只有通过平衡许多相互竞争的关注点才能找到类的“最佳”接口,而封装程度只是其中之一。”

如果某个函数作为成员函数“有意义”,则将其设为成员函数。同样,如果它实际上不是主界面的一部分,并且成为非会员“才有意义”,那就这样做。

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

我不认为这样说是错误的:Sutter、Alexandrescu 和 Meyers 在 C++ 质量方面所做的工作比其他任何人都多。

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

如果一个实用函数有两个独立的类作为参数,那么哪个类应该“拥有”该成员函数?

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

对于这两个示例,您的 IDE 将提供不完整的信息,您将不得不使用“旧时尚方式”。

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

您的 IDE 可能会在一两个版本中发生变化,您甚至可以让他们添加此功能。如果您更改编码风格以适应当今的 IDE,您很可能会发现将来会因无法扩展/无法维护的代码而遇到更大的问题。

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

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

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

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

如果您给它们一个共同的前缀,那么您的 IDE 可能会有所帮助(如果您输入)

::prefix

或者

namespace::prefix

在许多 OOP 语言中,非友元非类方法是三等公民,位于与任何事物无关的孤儿院中。当我编写方法时,我喜欢选择好的父母 - 一个合适的班级 - 他们最有机会感到受欢迎和帮助。

我本以为 IDE 实际上可以帮助您。

集成开发环境 正在隐藏 受保护的函数 从列表中删除,因为它们不可用于 民众 正如班级设计者的意图。

如果您已在课程范围内并输入 这-> 然后受保护的功能将显示在列表中。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top