繁体   English   中英

非朋友,非成员函数会增加封装?

[英]Non-friend, non-member functions increase encapsulation?

非成员函数如何改善封装的文章中,Scott Meyers提出无法阻止非成员函数“发生”。

语法问题

如果您像我与之讨论过这个问题的许多人一样,您可能会对我的建议的句法含义有所保留,即即使您购买了我的朋友,非朋友非成员函数也应优先于成员函数关于封装的争论。 例如,假设Wombat类支持进食和睡眠功能。 进一步假设进餐功能必须被实现为成员功能,但是睡眠功能可以被实现为成员或非朋友非成员功能。 如果您遵循我的建议,您将声明如下内容:

 class Wombat { public: void eat(double tonsToEat); void sleep(double hoursToSnooze); }; w.eat(.564); w.sleep(2.57); 

啊,一切都统一! 但是这种统一是令人误解的,因为世界上有比您的哲学所梦想的功能更多的功能。

坦率地说,非成员函数会发生。 让我们继续以袋熊为例。 假设您编写了对这些获取生物进行建模的软件,并想象经常需要袋熊做的一件事情就是睡了半个小时。 显然,您可以使用对w.sleep(.5)调用来w.sleep(.5)代码,但这要输入大量的.5,无论如何,如果魔术值发生了变化该怎么办? 有很多方法可以解决此问题,但也许最简单的方法是定义一个函数,该函数封装了您要执行的操作的详细信息。 假设您不是Wombat的作者,则该函数必须必须是non-member ,并且您必须这样调用它:

 void nap(Wombat& w) { w.sleep(.5); } Wombat w; nap(w); 

到了那里,可怕的语法不一致。 当您想要喂食袋熊时,可以进行成员函数调用,但是当您希望它们午睡时,可以进行非成员调用。

如果您反映了一点并且对自己诚实,那么您将承认与所有使用的非平凡类都存在这种所谓的不一致之处,因为没有一个类具有每个客户端所需的所有功能。 每个客户至少添加一些自己的便利功能,而这些功能始终是非成员的。 C ++程序员习惯了这一点,他们对此一无所知。 一些调用使用成员语法,而某些使用非成员语法。 人们只要查找适合他们要调用的函数的语法,然后就调用它们。 生活仍在继续。 它在标准C ++库的STL部分中尤其如此,其中一些算法是成员函数(例如,大小),一些算法是非成员函数(例如,唯一),而某些都是(例如,find)。 没有人眨眼。 甚至没有你

我真的不能把他的粗体/斜体字说的话全神贯注 为什么必须将其作为非成员实施? 为什么不只从Wombat类继承您自己的MyWombat类,并使nap()函数成为MyWombat的成员呢?

我刚开始使用C ++,但这就是我可能会用Java做到的方式。 这不是使用C ++的方法吗? 如果没有,为什么呢?

从理论上讲,您可以做到这一点,但您确实不想这么做。 让我们考虑一下为什么您不想这样做(目前,在原始上下文中-C ++ 98/03,而忽略了C ++ 11和更高版本中的新增功能)。

首先,这意味着基本上所有类都必须编写为充当基类,但是对于某些类来说,这只是一个糟糕的想法,甚至可能与基本意图背道而驰(例如,旨在实现的某些东西) Flyweight模式)。

其次,它将使大多数继承毫无意义。 举一个明显的例子,C ++中的许多类都支持I / O。 就目前而言,惯用的方法是将operator<<operator>>重载为自由函数。 目前,iostream的目的是代表至少某种类似于文件的东西-我们可以在其中写入数据,和/或我们可以从其中读取数据的东西。 如果我们通过继承支持I / O,那也意味着可以从任何模糊的文件中读取/写入的内容。

这根本没有任何意义。 iostream至少代表某种类似于文件的东西,而不是您可能想要从文件读取或写入文件的所有对象。

更糟糕的是,这将使几乎所有编译器的类型检查几乎毫无意义。 仅举例来说,将distance对象写入person对象没有任何意义-但是,如果它们都通过从iostream派生来支持I / O,则编译器将无法从真正使感。

不幸的是,那只是冰山一角。 从基类继承时,您将继承该基类的限制 例如,如果您使用的基类不支持副本分配或副本构造,则派生类的对象也不会/也不能够。

继续前面的示例,这意味着如果要对对象执行I / O,则不能支持该类型对象的副本构造或副本分配。

反过来,这意味着支持I / O的对象将与支持放入集合中的对象脱节(即,集合需要 iostream禁止的功能)。

底线:我们几乎立即以彻底难以处理的混乱结束,在此混乱中,我们的继承都不再具有任何实际意义,并且编译器的类型检查将几乎完全变得无用。

因为这样您就在新类和原始Wombat之间建立了非常强烈的依赖关系。 继承不一定是好的。 它是C ++中任何两个实体之间第二强的关系。 只有friend声明更强。

我认为当Meyers首次发表该文章时,我们大多数人都采取了双重行动,但目前人们普遍认为它是真的。 在现代C的世界++你的第一反应应该是从类派生。 除非您要添加确实现有类专业化的新类, 否则派生是万不得已的方法。

Java中的事项有所不同。 您在那里继承。 您真的别无选择。

正如杰里·科芬(Jerry Coffin)所描述的那样,您的想法并不全面,但是对于不属于层次结构的简单类(例如此处的Wombat ,它是可行的。

但是,有一些危险需要提防:

  • 切片 -如果存在一个按值接受Wombat的函数,则您必须切断myWombat的额外附件,并且它们不会重新增长。 在Java中通过引用传递所有对象的情况下不会发生这种情况。

  • 基类指针 -如果Wombat是非多态的(即没有v-table),则意味着您不能轻松地在容器中混合WombatmyWombat 删除指针不会正确删除myWombat品种。 (但是,您可以使用shared_ptr来跟踪自定义删除器)。

  • 类型不匹配 :如果编写任何接受myWombat函数,则无法使用Wombat调用它们。 另一方面,如果编写函数以接受Wombat则不能使用myWombat的语法糖。 投射无法解决此问题; 您的代码将无法与界面的其他部分正确交互。

避免所有这些危险的一种方法是使用包容而不是继承myWombat将具有Wombat私有成员,并且为要公开的任何Wombat属性编写转发功能。 myWombat类的设计和维护方面,这是更多工作。 但是它消除了任何人错误使用您的类的可能性,并且使您能够解决诸如所包含的类不可复制之类的问题。


对于层次结构中的多态对象,尽管类型不匹配问题仍然存在,但您没有切片和基类指针问题。 实际上,情况更糟。 假设层次结构为:

Animal <-- Marsupial <-- Wombat <-- NorthernHairyNosedWombat

您可以从Wombat派生myWombat 但是,这意味着NorthernHairyNosedWombatmyWombat的同胞,而它是Wombat的子代。

因此,无论如何, NorthernHairyNosedWombat都无法使用添加到myWombat中的任何漂亮的myWombat函数。


简介:恕我直言,这些好处不值得它留下的烂摊子。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM