提问人:Carl Seleborg 提问时间:9/26/2008 最后编辑:jopCarl Seleborg 更新时间:6/29/2009 访问量:924
类设计与 IDE:非成员非友元函数真的值得吗?
Class design vs. IDE: Are nonmember nonfriend functions really worth it?
问:
在(否则)优秀的书《C++编码标准》第44项中,标题为“首选编写非成员非友元函数”,Sutter和Alexandrescu建议只有真正需要访问类成员的函数才是该类的成员。只能使用成员函数编写的所有其他操作都不应是该类的一部分。他们应该是非会员和非朋友。论点是:
- 它促进了封装,因为需要访问类内部的代码更少。
- 它使编写函数模板更容易,因为您不必每次都猜测某个函数是否是成员。
- 它使类保持较小,这反过来又使其更容易测试和维护。
虽然我看到了这些参数的价值,但我看到了一个巨大的缺点:我的IDE无法帮助我找到这些函数!每当我有某种对象,并且我想查看它上面有哪些可用的操作时,我就不能再只键入“”并获取成员函数列表了。pMysteriousObject->
保持简洁的设计归根结底是为了让你的编程生活更轻松。但这实际上会让我更难。
所以我想知道这是否真的值得麻烦。你是怎么处理的?
答:
在这一点上,我将不得不不同意萨特和亚历山德莱斯库的观点。我认为,如果函数的行为属于类的职责范围,那么就应该是 的一部分。foo()
Bar
foo()
bar()
不需要直接访问的成员数据并不意味着它在概念上不是 .这也可能意味着代码的分解很好。成员函数通过其他成员函数执行其所有行为的情况并不少见,我不明白为什么应该这样做。foo()
Bar
Bar
我完全同意与外围相关的函数不应该成为类的一部分,但如果某些东西是类职责的核心,那么它没有理由不应该成为成员,无论它是否直接与成员数据混在一起。
至于这些具体要点:
它促进了封装,因为需要访问类内部的代码更少。
事实上,直接访问内部的功能越少越好。这意味着让成员函数通过其他成员函数尽可能多地做是一件好事。将因式分解良好的函数从类中分离出来只会留下一个半类,这需要一堆外部函数才能有用。将因式分解良好的函数从类中抽离出来似乎也会阻碍编写因式分解的函数。
它使编写函数模板更容易,因为您不必每次都猜测某个函数是否是成员。
我完全不明白这一点。如果你从类中拉出一堆函数,你就把更多的责任推给了函数模板。他们被迫假设他们的类模板参数提供的功能更少,除非我们假设从他们的类中提取的大多数函数将被转换为模板(呃)。
它使类保持较小,这反过来又使其更容易测试和维护。
嗯,当然。它还创建了许多额外的外部功能来测试和维护。我看不到其中的价值。
评论
如果你给它们一个通用的前缀,那么如果你键入
::prefix
或
namespace::prefix
评论
确实,外部函数不应成为接口的一部分。从理论上讲,你的类应该只包含数据,并公开它的预期接口,而不是实用函数。向接口添加实用函数只会增加类代码库并使其难以维护。我目前维护着一个拥有大约 50 种公共方法的课程,这简直太疯狂了。
现在,实际上,我同意这并不容易执行。向类添加另一个方法通常更容易,如果您使用的 IDE 实际上可以简单地将新方法添加到现有类,则更是如此。
为了保持我的类简单,并且仍然能够集中外部函数,我经常使用与我的类甚至命名空间一起工作的实用程序类。 我首先创建一个类,该类将包装我的数据并公开最简单的接口。然后,我为与该类相关的每个任务创建一个新类。
示例:创建一个类 Point,然后添加一个类 PointDrawer 以将其绘制到位图,添加 PointSerializer 以保存它,以此类推。
在许多 OOP 语言中,非友元、非类方法是居住在孤儿院的三等公民,与任何事物都没有联系。当我写一种方法时,我喜欢选择好的父母——一个合适的班级——他们有最好的机会感到受欢迎和帮助。
我本来以为IDE实际上是在帮助你。
IDE 从列表中隐藏了受保护的函数,因为这些函数不对公众开放,正如类的设计者所希望的那样。
如果您在类范围内并键入了 this->则受保护的函数将显示在列表中。
斯科特·迈耶斯(Scott Meyers)与萨特(Sutter)有类似的观点,请参阅此处。
他还明确指出:
“基于他对各种类字符串类的研究,Jack Reeves 观察到,有些函数在成为非成员时会”感觉“不对,即使它们可能是非朋友的非成员。只有通过平衡许多相互竞争的问题,才能找到一个类的“最佳”接口,其中封装程度只是其中之一。
如果一个函数“有意义”成为成员函数,那就让它成为一个。同样,如果它不是主界面的一部分,并且成为非成员“很有意义”,那就这样做。
需要注意的是,对于 例如 operator==() 的重载版本,语法保持不变。因此,在这种情况下,您没有理由不将其作为与类在同一位置声明的非成员非友元浮动函数,除非它确实需要访问私有成员(根据我的经验,它很少会)。即便如此,您也可以将 operator!=() 定义为非成员,并且用 operator==() 来定义。
我认为说他们之间,Sutter,Alexandrescu和Meyers为C++的质量所做的工作比其他任何人都多,这并没有错。
他们问的一个简单的问题是:
如果一个实用程序函数有两个独立的类作为参数,哪个类应该“拥有”成员函数?
另一个问题是,您只能在相关类由您控制的情况下添加成员函数。为 std::string 编写的任何帮助程序函数都必须是非成员,因为无法重新打开类定义。
对于这两个示例,您的IDE将提供不完整的信息,您将不得不使用“老式方式”。
鉴于世界上最有影响力的C++专家认为具有类参数的非成员函数是类接口的一部分,这更像是IDE的问题,而不是编码风格的问题。
您的 IDE 可能会在一两个版本中发生变化,您甚至可以让他们添加此功能。如果你改变你的编码风格以适应今天的IDE,你很可能会发现你将来会遇到更大的问题,即不可扩展/不可维护的代码。
评论