有效的C ++项目23首选非成员非友元函数到成员函数

时间:2011-05-13 09:14:56

标签: c++ encapsulation member-functions non-member-functions effective-c++

虽然对类设计中的一些事实感到困惑,特别是函数是否应该是成员,但我查看了有效的c ++并找到了第23项,即将非成员非友元函数更喜欢成员函数。第一手阅读Web浏览器示例有一定意义,但是该示例中的便捷函数(在本书中命名为非成员函数)会改变类的状态,不是吗?

  • 那么,第一个问题,他们不应该成为会员吗?

  • 进一步阅读,他认为STL函数和实际上某些类未实现的函数是在stl中实现的。根据本书的思想,它们演变成一些便利函数,这些函数被打包到一些合理的命名空间中,例如来自std::sort的{​​{1}},std::copy。例如,algorithm类没有vector函数,并且使用stl sort函数,因此它不是向量类的成员。但是也可以将相同的推理延伸到向量类中的一些其他函数,例如sort,这样也不能作为成员实现,而是作为便利函数实现。但是,这也会改变对象的内部状态,例如它操作的排序。那么这个微妙但重要(我猜)问题背后的理由是什么呢?

如果你有权访问这本书,你可以为我澄清这些要点吗?

7 个答案:

答案 0 :(得分:36)

绝对没有必要访问这本书。

我们在这里处理的问题是依赖重用

在精心设计的软件中,您尝试将项目彼此隔离以减少依赖关系,因为在需要更改时,依赖关系是一个需要克服的障碍。

在精心设计的软件中,您应用 DRY 原则(不要重复自己),因为当需要进行更改时,必须在十几个重复中进行更改是非常痛苦且容易出错的不同的地方。

“经典”OO思维模式在处理依赖关系方面越来越糟糕。通过直接依赖于类的内部的许多方法,最轻微的改变意味着整个重写。不一定如此。

在C ++中,STL(不是整个标准库)的设计目标明确:

  • 切割依赖关系
  • 允许重用

因此,容器公开了明确定义的接口,这些接口隐藏了它们的内部表示,但仍然提供了对它们封装的信息的足够访问,以便可以对它们执行算法。所有修改都通过容器接口进行,以保证不变量。

例如,如果您考虑sort算法的要求。对于STL使用的(通常)实现,它需要(来自容器):

  • 有效访问给定索引处的项目:随机访问
  • 交换两个项目的能力:非关联

因此,任何提供随机访问且不是关联的容器(理论上)都适合通过(比如)快速排序算法进行有效排序。

C ++中满足此要求的容器是什么?

  • 基本C-array
  • 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)

各种想法:

  • 当非成员通过该类的公共API工作时很好,因为它减少了以下代码的数量:
      需要仔细监控
    • 以确保类不变量,
    • 如果重新设计了对象的实现,则需要更改
  • 如果不够好,非成员仍然可以成为friend
  • 编写非成员函数通常不太方便,因为成员不是隐含在范围内,但是如果考虑程序演变:
    • 在一个非成员函数存在,人们意识到,相同的功能将是其他类型的有用的,它通常是很容易的功能转换为模板,可将其不只是这两种类型,但对于将来的任意类型也是。换句话说,非成员模板允许比运行时多态/虚拟调度更灵活的算法重用:模板允许称为duck typing
    • 现有类型运动一个有用的成员函数的鼓励剪切和粘贴以与其它类型的想类似的行为,因为转换功能,用于再利用的大多数方法需要每个隐成员访问对某个特定对象进行显式访问,对于程序员来说这将是一个更长的30秒以上....
  • 会员功能允许object.function(x, y, z)符号,恕我直言,非常方便,直观。它们还可以在许多IDE中使用发现/完成功能更好地工作。
  • 一个分离为成员和非成员函数可以帮助通信的类的基本性质,这是不变量和基本操作,以及逻辑组的附加和可能的ad-hoc“方便”的特征。考虑东尼·霍尔的智慧:

    “有构建软件设计的两种方法:一种方法是让它尽量简单,让人看不出明显的不足之处,而另一种方法是使它很复杂,没有明显的缺陷。第一种方法要困难得多。“

    • 在这里,非成员使用并不一定要困难得多,但您必须更多地考虑如何访问成员数据和私有/受保护方法以及原因,以及哪些操作是基础。这样的灵魂搜索也可以通过成员函数改进设计,它更容易变得懒惰: - /。
  • 作为非成员的功能在复杂程度膨胀或拿起附加的依赖关系,可以将这些功能移到单独的标题和执行文件,甚至库,所以的核心功能唯一的“支付”的用户使用所述零件他们想。

(Omnifarious的答案是必须阅读的,如果对你来说是新的话,则是三次。)

答案 6 :(得分:1)

我认为sort不是作为成员函数实现的,因为它被广泛使用,不仅仅用于向量。 如果他们将它作为一个成员函数,那么每次使用它时,他们都必须重新实现它。所以我认为这是为了更容易实现。