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

Umu*_*bak 35 c++ encapsulation member-functions effective-c++ non-member-functions

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

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

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

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

Mat*_* M. 37

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

我们在这里讨论的问题是依赖重用.

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

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

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

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

  • 削减依赖性
  • 允许重用

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

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

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

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

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

  • 基本的C阵列
  • deque
  • vector

如果你注意这些细节,可以写的任何容器.

sort为每个人重写(复制/粘贴/调整)会是浪费,不是吗?

请注意,例如,有一种std::list::sort方法.为什么?因为std::list不提供随机访问(非正式地myList[4]不起作用),因此sortfrom算法不适合.

  • @SomeGuy:你在技术上是正确的,但也没有抓住重点。成员函数_可以_访问内部,而非成员非友元则不能。即使他们现在不这样做,将来也可能会这样做。因此,建议通过设计推动更高的封装——推广必须保持不变量的最小接口。 (3认同)
  • @SomeGuy:另一方面,非成员函数有一个明确的优势=>模板非成员函数是可重用的。OP 中提到的 STL 算法就是一个很好的例子,如果可以避免的话,没有人愿意为每个容器重写“sort”。更进一步,ADL 使得无缝调用通用模板函数或模板函数中的专用函数成为可能——这与成员函数的工作方式不同——一个主要的例子是 `use std::swap ; 交换(x,y);`。该指南具有简单性和可组合性的优点。 (2认同)

Omn*_*ous 19

我使用的标准是,如果一个函数可以通过成员函数显着更有效地实现,那么它应该是一个成员函数.::std::sort不符合这个定义.事实上,在外部和内部实施它没有任何效率差异.

通过实现成员(或朋友)功能来提高效率意味着通过了解类的内部状态可以大大提高效率.

界面设计的一部分技术是找到最小的成员函数集,这样您可能希望在对象上执行的所有操作都可以合理有效地实现.而且这个集合不应该支持不应该在类上执行的操作.因此,您不能只实现一堆getter和setter函数并将其称为好.

  • +1表示"不应支持不应执行的操作" (2认同)

asc*_*bol 11

我认为这个规则的原因是通过使用成员函数,你可能会偶然地依赖于类的内部.改变类的状态不是问题.真正的问题是,如果修改类中的某些私有属性,则需要更改的代码量.保持类(公共方法)的接口尽可能小,既减少了在这种情况下需要做的工作量,又减少了对私有数据做一些奇怪的事情的风险,使得实例处于不一致状态.

AtoMerZ也是对的,非成员非朋友函数也可以模板化并重用于其他类型.

顺便说一句,你应该购买你的有效C++的副本,这是一本很棒的书,但不要总是遵守本书的每一项.面向对象设计既有良好的实践(来自书籍等)和经验(我认为它也是用有效的C++编写的).

  • 并且不要总是遵循 C++ 中的面向对象设计指南,它是多范式的,所以有些东西最好用其他方式表达。 (2认同)

Ton*_*roy 6

各种想法:

  • 当非成员通过类的公共 API 工作时,这很好,因为它减少了以下代码量:
    • 需要仔细监控以确保类不变量,
    • 如果重新设计对象的实现,则需要更改。
  • 当这还不够好时,非会员仍然可以成为friend.
  • 编写非成员函数通常不太方便,因为成员不在范围内,但是如果您考虑程序演变:
    • 一旦存在非成员函数并且意识到相同的功能对其他类型有用,通常很容易将函数转换为模板并使其不仅可用于这两种类型,而且还可用于任意未来类型。换句话说,非成员模板允许比运行时多态性/虚拟分派更灵活的算法重用:模板允许称为鸭子类型的东西。
    • 具有有用成员函数的现有类型鼓励剪切并粘贴到其他喜欢类似行为的类型,因为大多数将函数转换为重用的方法都要求对特定对象的每个隐式成员访问进行显式访问,对于程序员来说,这将是一个更乏味的 30+ 秒......
  • 成员函数允许object.function(x, y, z)符号,恕我直言,这是非常方便、富有表现力和直观的。它们还可以更好地与许多 IDE 中的发现/完成功能配合使用。
  • 成员函数和非成员函数的分离有助于传达类的基本性质、不变量和基本操作,并在逻辑上对附加组件和可能的临时“便利”功能进行分组。想想托尼·霍尔 (Tony Hoare) 的智慧:

    “构建软件设计有两种方法:一种方法是使其简单到没有明显缺陷,另一种方法是使其复杂到没有明显缺陷。第一种方法方法要困难得多。”

    • 在这里,非成员使用不一定要困难得多,但您必须更多地考虑如何访问成员数据和私有/受保护的方法以及原因,以及哪些操作是基本的。这样的灵魂搜索也会改进成员函数的设计,只是更容易偷懒:-/。
  • 随着非成员功能的复杂性扩展或获取额外的依赖项,这些功能可以移到单独的头文件和实现文件中,甚至是库中,因此核心功能的用户只需为使用他们想要的部分“付费”。

(Omnifarious 的答案是必读的,如果对您来说是新的,请阅读三次。)