虽然对类设计中的一些事实感到困惑,特别是函数是否应该是成员,但我查看了有效的c ++并找到了第23项,即将非成员非友元函数更喜欢成员函数。第一手阅读Web浏览器示例有一定意义,但是该示例中的便捷函数(在本书中命名为非成员函数)会改变类的状态,不是吗?
那么,第一个问题,他们不应该成为会员吗?
进一步阅读,他认为STL函数和实际上某些类未实现的函数是在stl中实现的。根据本书的思想,它们演变成一些便利函数,这些函数被打包到一些合理的命名空间中,例如来自std::sort
的{{1}},std::copy
。例如,algorithm
类没有vector
函数,并且使用stl sort
函数,因此它不是向量类的成员。但是也可以将相同的推理延伸到向量类中的一些其他函数,例如sort
,这样也不能作为成员实现,而是作为便利函数实现。但是,这也会改变对象的内部状态,例如它操作的排序。那么这个微妙但重要(我猜)问题背后的理由是什么呢?
如果你有权访问这本书,你可以为我澄清这些要点吗?
答案 0 :(得分:36)
绝对没有必要访问这本书。
我们在这里处理的问题是依赖和重用。
在精心设计的软件中,您尝试将项目彼此隔离以减少依赖关系,因为在需要更改时,依赖关系是一个需要克服的障碍。
在精心设计的软件中,您应用 DRY 原则(不要重复自己),因为当需要进行更改时,必须在十几个重复中进行更改是非常痛苦且容易出错的不同的地方。
“经典”OO思维模式在处理依赖关系方面越来越糟糕。通过直接依赖于类的内部的许多方法,最轻微的改变意味着整个重写。不一定如此。
在C ++中,STL(不是整个标准库)的设计目标明确:
因此,容器公开了明确定义的接口,这些接口隐藏了它们的内部表示,但仍然提供了对它们封装的信息的足够访问,以便可以对它们执行算法。所有修改都通过容器接口进行,以保证不变量。
例如,如果您考虑sort
算法的要求。对于STL使用的(通常)实现,它需要(来自容器):
因此,任何提供随机访问且不是关联的容器(理论上)都适合通过(比如)快速排序算法进行有效排序。
C ++中满足此要求的容器是什么?
deque
vector
如果你注意这些细节,你的任何容器都可以写。
为每个人重写(复制/粘贴/调整)sort
会不会浪费,不是吗?
请注意,例如,有std::list::sort
方法。为什么?由于std::list
不提供随机访问(非正式myList[4]
不起作用),因此算法中的sort
不合适。
答案 1 :(得分:19)
我使用的标准是,如果一个函数可以通过成为一个成员函数显着更有效地实现,那么它应该是一个成员函数。 ::std::sort
不符合该定义。事实上,在外部和内部实施它没有任何效率差异。
通过实现成员(或朋友)功能来提高效率意味着通过了解班级的内部状态可以大大提高效率。
界面设计的一部分技术是找到最小的成员函数集,这样您可能希望在对象上执行的所有操作都可以合理有效地实现。而且这个集合不应该支持不应该在类上执行的操作。所以你不能只实现一堆getter和setter函数并称之为好。
答案 2 :(得分:11)
我认为这个规则的原因是,通过使用成员函数,您可能会意外地依赖于类的内部。改变类的状态不是问题。真正的问题是,如果修改类中的某些私有属性,则需要更改的代码量。保持类(公共方法)的接口尽可能小,既减少了在这种情况下需要做的工作量,也减少了对私有数据做一些奇怪的事情的风险,使得实例处于不一致状态。
AtoMerZ也是对的,非成员非朋友函数也可以模板化并重用于其他类型。
顺便说一句,你应该购买你的有效C ++副本,这本书很棒,但不要总是遵守本书的每一项。面向对象设计既有良好的实践(来自书籍等)和经验(我认为它也是用有效的C ++编写的)。
答案 3 :(得分:3)
所以,第一个问题,不应该是 成员比?
不,这不遵循。在惯用的C ++类设计中(至少在 Effective C ++ 中使用的习语中),非成员非友元函数扩展了类接口。它们可以被视为该类的公共API的一部分,尽管它们不需要也不具有对该类的私有访问权。如果这个设计是OOP的某些定义的“非OOP”那么,那么,惯用的C ++不是那个定义的OOP。
向某些人伸出同样的理由 vector类
中的其他函数
确实如此,标准容器的某些成员函数可以是自由函数。例如,vector::push_back
是根据insert
定义的,当然可以在没有私有访问类的情况下实现。但是,在这种情况下,push_back
是抽象概念的一部分,即BackInsertionSequence
,该向量实现。这些通用概念贯穿于特定类的设计,因此,如果您正在设计或实现可能会影响函数放置位置的通用概念。
当然,标准的某些部分应该是不同的,例如std::string has way too many member functions。但是已经完成了什么,这些类是在人们真正适应我们现在称之为现代C ++风格之前设计的。这个课程无论哪种方式都有效,所以只要担心差异就可以获得如此多的实际好处。
答案 4 :(得分:2)
动机很简单:保持一致的语法。作为班级
进化或使用,各种非会员便利功能将
出现;你不想修改类接口来添加东西
比如toUpper
到字符串类。 (如果是
std::string
,当然,你不能。)斯科特担心的是,当这个
发生这种情况,你最终会得到不一致的语法:
s.insert( "abc" );
toUpper( s );
只使用免费功能,根据需要声明好友,全部 函数具有相同的语法。另一种方法是修改 每次添加便利功能时的类定义。
我并不完全相信。如果一个班级设计得很好,它就有一个基础 功能,用户清楚哪些功能是其中的一部分 基本功能,以及其他便利功能 (如果有的话)。在全球范围内,字符串是一种特殊情况, 因为它被设计用于解决许多不同的问题; 我无法想象很多课程都是如此。
答案 5 :(得分:2)
各种想法:
friend
。object.function(x, y, z)
符号,恕我直言,非常方便,直观。它们还可以在许多IDE中使用发现/完成功能更好地工作。一个分离为成员和非成员函数可以帮助通信的类的基本性质,这是不变量和基本操作,以及逻辑组的附加和可能的ad-hoc“方便”的特征。考虑东尼·霍尔的智慧:
“有构建软件设计的两种方法:一种方法是让它尽量简单,让人看不出明显的不足之处,而另一种方法是使它很复杂,没有明显的缺陷。第一种方法要困难得多。“
作为非成员的功能在复杂程度膨胀或拿起附加的依赖关系,可以将这些功能移到单独的标题和执行文件,甚至库,所以的核心功能唯一的“支付”的用户使用所述零件他们想。
(Omnifarious的答案是必须阅读的,如果对你来说是新的话,则是三次。)
答案 6 :(得分:1)
我认为sort不是作为成员函数实现的,因为它被广泛使用,不仅仅用于向量。 如果他们将它作为一个成员函数,那么每次使用它时,他们都必须重新实现它。所以我认为这是为了更容易实现。