有效的C ++项目23首选成员函数的非成员非友元函数
对于课堂devise中的一些事实,特别是function是否应该是成员,我考虑了一下Effective c ++,find了第23项,即非成员非好友函数。 用Web浏览器的例子第一手的阅读是有道理的,然而在这个例子中,便利的function(在这本书中命名为非成员函数)改变了这个类的状态,不是吗?
-
所以,第一个问题,他们不应该是成员吗?
-
进一步阅读,他认为STL函数,实际上一些类没有实现的函数都是在stl中实现的。 按照本书的思想,它们演变成一些便利的function,它们被压缩到一些合理的命名空间中,比如
std::sort
,std::copy
fromalgorithm
。 例如,vector
类没有sort
函数,而使用stlsort
函数,所以不是vector类的成员。 但是也可以将相同的推理延伸到vector类中的其他一些函数,比如assign
这样也可以不作为成员实现,而是作为一个便利函数。 但是,这也会改变对象的内部状态,就像它所操作的那样。 那么这个微妙但重要的(我猜)问题背后的理由是什么?
如果你有机会获得这本书,你能否为我澄清这些问题?
访问这本书是没有必要的。
我们在这里处理的问题是依赖和重用 。
在一个devise良好的软件中,你试图隔离项目,以减less依赖,因为依赖是一个需要改变的障碍。
在一个devise良好的软件中,应用DRY原则(不要重复自己),因为当需要更改时,这是十分痛苦和容易出错的,必须在十几个不同的地方重复。
面向对象的“经典”面向对象思维越来越糟糕。 由于有很多很多的方法直接取决于课程的内部,所以稍微改变意味着整个重写。 它不一定如此。
在C ++中,STL(不是整个标准库)的devise具有以下明确的目标:
- 切割依赖关系
- 允许重用
因此,容器公开了定义良好的接口来隐藏它们的内部表示,但是仍然提供对它们封装的信息的充分访问,以便可以在其上执行algorithm。 所有的修改都通过容器界面进行,以保证不variables。
例如,如果您考虑sort
algorithm的要求。 对于STL使用(一般)的实现,它要求(从容器):
- 有效访问给定索引处的项目:随机访问
- 交换两个项目的能力:不关联
因此,任何提供随机访问且不关联的容器(理论上)都适合通过(比方说)快速sortingalgorithm进行有效sorting。
什么是在C + +的容器,满足这一点?
- 基本的Carrays
-
deque
-
vector
如果你注意到这些细节, 你可以写任何容器。
为每一个人重新编写(复制/粘贴/调整) sort
会是浪费的吗?
请注意,例如,有一个std::list::sort
方法。 为什么? 因为std::list
不提供随机访问(非正式myList[4]
不起作用),因此algorithm的sort
是不合适的。
我使用的标准是如果一个函数可以通过成员函数更有效地实现,那么它应该是一个成员函数。 ::std::sort
不符合该定义。 事实上,在外部和内部实施它们没有效率差异。
通过实现作为成员(或朋友)function的东西,可以大大提高效率,这意味着通过了解class级的内部状态可以大大地提高效率。
界面devise的一部分艺术是find最小的一组成员函数,使得你可能想要在对象上执行的所有操作都可以合理有效地实现。 而这套教材不应该支持不应该在课堂上进行的操作。 所以你不能只执行一堆getter和setter函数,并称它为好。
我认为这个规则的原因是,通过使用成员函数,你可能过分依赖一个class级的内部意外。 改变一个class级的状态不是问题。 真正的问题是如果您在类中修改某些私有属性,则需要更改代码量。 保持类(公共方法)的接口尽可能小,既减less了在这种情况下你需要做的工作量,也减less了对你的私有数据做奇怪事情的风险,让你的实例处于不一致的状态。
AtoMerZ也是对的,非会员的非朋友function也可以被模板化并重用于其他types。
顺便说一句,你应该购买Effective C ++的副本,这本书是一本好书,但不要总是遵守本书的每一个条款。 面向对象devise良好的实践(从书籍等)和经验(我认为它也写在有效的C + +的地方)。
所以,第一个问题,他们不应该是成员吗?
不,这不关。 在惯用的C ++类devise中(至less在Effective C ++中使用的成语中),非成员非友元函数扩展了类接口。 尽pipe他们不需要和不能私下接触课程,但他们可以被认为是该课程的公共API的一部分。 如果这个devise是由OOP的某个定义“不是OOP”的话,OK,惯用的C ++不是OOP的定义。
向量类中的一些其他函数也是一样的推理
这是真的,有一些标准容器的成员函数可以是免费的function。 例如, vector::push_back
是按照insert
来定义的,当然可以在没有对类的私有访问的情况下实现。 然而在这种情况下, push_back
是抽象概念BackInsertionSequence
一部分。 这样的通用概念贯穿特定类的devise,所以如果你正在devise或实现你自己的通用概念,可能会影响你放置函数的位置。
当然,这个标准的部分内容可能会有所不同,例如std :: string有太多的成员函数 。 但是所做的事情已经完成了,这些类是在人们真正安定下来之前devise的,现在我们可以称之为现代C ++风格。 课程无论如何,所以只有这么多的实际好处,你可以得到担忧的差异。
动机很简单:保持一致的语法。 随着class级的演进或使用,各种非会员便利function将出现; 例如,您不想修改类接口来添加类似toUpper
的string类。 (当然,在std::string
的情况下,你不能。)Scott的担心是,当这种情况发生的时候,你会得到不一致的语法:
s.insert( "abc" ); toUpper( s );
通过只使用免费函数,根据需要声明他们的朋友,所有function都有相同的语法。 另一种方法是在每次添加便利function时修改类定义。
我不完全相信。 如果一个类devise得很好,它有一个基本的function,用户清楚哪些function是基本function的一部分,哪些是附加的便利function(如果存在的话)。 在全球范围内,string是一种特殊情况,因为它被devise用来解决许多不同的问题; 我无法想象这是许多类的情况。
各种想法:
- 非成员通过类的公共API工作是非常好的,因为它减less了代码的数量:
- 需要仔细监测,以确保类不variables,
- 如果对象的实现被重新devise,则需要改变。
- 如果不够好,非成员仍然可以成为
friend
。 - 编写一个非成员函数通常是一个不太方便的smidgeon,因为成员并不是隐含在范围之内,但是如果考虑程序的演变:
- 一旦存在一个非成员函数,并且意识到相同的function对其他types是有用的,那么将函数转换为模板通常非常容易,并且不仅适用于这两种types,而且还适用于任意将来的types。 换句话说,非成员模板允许比运行时多态/虚拟调度更灵活的algorithm重用:模板允许称为鸭子打字的东西。
- 一个有用的成员函数的现有types鼓励剪切和粘贴到类似行为的其他types,因为大多数转换函数重用的方法要求每个隐式成员访问被明确访问一个特定的对象,对于程序员来说,这将是一个更麻烦的30多秒。
- 成员函数允许
object.function(x, y, z)
符号,这是恕我直言,非常方便,expression和直观。 在许多IDE中,它们还可以更好地发现/完成function。 -
作为成员和非成员职能的分离可以帮助传达类的基本性质,它是不variables和基本操作,并逻辑上将附加组件和可能特设的“便利”特征分组。 考虑托尼霍尔的智慧:
“构build软件devise有两种方法:一种方法是使其简单明了,没有明显的缺陷,另一种方法是使其如此复杂以至于没有明显的缺陷。第一种方法要困难得多“。
- 在这里,非成员用法并不一定困难得多,但是您必须更多地考虑如何访问成员数据和私有/受保护的方法,以及为什么以及哪些操作是基本的。 这样的灵魂search也可以用成员函数来改进devise,对于: – /懒惰更容易。
-
随着非成员function的复杂性增加或者获得更多的依赖关系,这些function可以被移动到单独的头文件和实现文件中,甚至是库文件中,所以核心function的用户只需要“付费”就可以使用他们想要的部分。
(Omnifarious的答案是一个必须阅读的,如果对你来说是新的,那就是三次)
我认为sorting不是作为一个成员函数来实现的,因为它被广泛使用,不仅仅是用于向量。 如果他们把它作为一个成员函数,那么每次使用它的每个容器都必须重新实现它。 所以我认为这是更容易实施。