虽然对类设计中的一些事实感到困惑,特别是函数是否应该是成员,但我查看了有效的c ++并找到了第23项,即将非成员非友元函数更喜欢成员函数。第一手阅读Web浏览器示例有一定意义,但是该示例中的便捷函数(在本书中命名为非成员函数)会改变类的状态,不是吗?
std::sort
的std::copy
,algorithm
。例如,vector
类没有sort
函数,并且使用stl sort
函数,因此它不是vector类的成员。但是也可以将相同的推理延伸到矢量类中的一些其他函数,例如assign
,这样也不能作为成员实现,而是作为便利函数实现。但是,这也会改变对象的内部状态,例如它操作的排序。那么这个微妙但重要(我猜)问题背后的理由是什么呢?如果你有权访问这本书,你可以为我澄清这些要点吗?
绝对没有必要访问这本书。
我们在这里讨论的问题是依赖和重用。
在设计良好的软件中,您尝试将项目彼此隔离以减少依赖关系,因为在需要更改时,依赖关系是一个需要克服的障碍。
在精心设计的软件中,您应用DRY原则(不要重复自己),因为当需要进行更改时,必须在十几个不同的地方重复它,这很痛苦,而且容易出错。
“经典”OO思维模式在处理依赖关系方面越来越糟糕。通过直接依赖于类的内部的许多方法,最轻微的改变意味着整个重写。不一定如此。
在C ++中,STL(不是整个标准库)的设计具有以下明确的目标:
因此,容器公开了明确定义的接口,这些接口隐藏了它们的内部表示,但仍然提供了对它们封装的信息的足够访问,以便可以在它们上执行算法。所有修改都通过容器接口进行,以保证不变量。
例如,如果您考虑sort
算法的要求。对于STL使用的(通常)实现,它需要(来自容器):
因此,任何提供随机访问且不是关联的容器(理论上)都适合通过(例如)快速排序算法进行有效排序。
C ++中满足此要求的容器是什么?
deque
vector
如果你注意这些细节,你可以写的任何容器。
为每个人重写(复制/粘贴/调整)sort
会是浪费,不是吗?
请注意,例如,有一个std::list::sort
方法。为什么?因为std::list
不提供随机访问(非正式地myList[4]
不起作用),因此来自算法的sort
不适合。
我使用的标准是,如果一个函数可以通过成员函数显着更有效地实现,那么它应该是一个成员函数。 ::std::sort
不符合这个定义。事实上,在外部和内部实施它没有任何效率差异。
通过实现成员(或朋友)功能来提高效率意味着通过了解类的内部状态可以大大提高效率。
界面设计的一部分技术是找到最小的成员函数集,这样您可能希望在对象上执行的所有操作都可以合理有效地实现。而且这个集合不应该支持不应该在类上执行的操作。因此,您不能只实现一堆getter和setter函数并将其称为好。
我认为这个规则的原因是通过使用成员函数,你可能会偶然地依赖于类的内部。改变类的状态不是问题。真正的问题是,如果修改类中的某些私有属性,则需要更改的代码量。保持类(公共方法)的接口尽可能小,既减少了在这种情况下需要做的工作量,又减少了对私有数据做一些奇怪的事情的风险,使得实例处于不一致状态。
AtoMerZ也是对的,非成员非朋友函数也可以模板化并重用于其他类型。
顺便说一句,你应该购买你的有效C ++的副本,这是一本很棒的书,但不要总是遵守本书的每一项。面向对象设计既有良好的实践(来自书籍等)和经验(我认为它也是用有效的C ++编写的)。
所以,第一个问题,他们不应该成员吗?
不,这不遵循。在惯用的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 ++风格之前设计的。这个课程无论哪种方式都有效,所以你可以从担心差异中获得如此多的实际好处。
动机很简单:保持一致的语法。随着课程的发展或使用,将出现各种非会员便利功能;例如,你不想修改类接口以将类似toUpper
的东西添加到字符串类中。 (在std::string
的情况下,当然,你不能。)斯科特担心的是,当发生这种情况时,你最终会得到不一致的语法:
s.insert( "abc" );
toUpper( s );
通过仅使用自由函数,根据需要声明它们的朋友,所有函数都具有相同的语法。另一种方法是在每次添加便利功能时修改类定义。
我并不完全相信。如果一个类设计得很好,它具有基本功能,用户可以清楚地知道哪些功能是该基本功能的一部分,哪些是附加的便利功能(如果有的话)。在全球范围内,字符串是一种特殊情况,因为它旨在用于解决许多不同的问题;我无法想象很多课程都是如此。
各种想法:
friend
。object.function(x, y, z)
表示法,其中恕我直言,非常方便,富有表现力和直觉。它们在许多IDE中的发现/完成功能方面也能更好地工作。(Omnifarious的答案是必读的,如果它对你来说是新的三倍。)
我认为sort不是作为成员函数实现的,因为它被广泛使用,不仅适用于向量。如果他们将它作为一个成员函数,那么每次使用它时,他们都必须重新实现它。所以我认为这是为了更容易实现。