读书笔记 effective c++ Item 23 宁可使用非成员非友元函数函数也不使用成员函数
1. 非成员非友元好还是成员函数好?
想象一个表示web浏览器的类。这样一个类提供了清除下载缓存,清除URL访问历史,从系统中移除所有cookies等接口:
class WebBrowser {
public:
...
void clearCache();
void clearHistory();
void removeCookies();
...
};
许多用户想将这些动作一块执行,所以web浏览器为此可以提供一个函数:
class WebBrowser {
public:
...
void clearEverything(); // calls clearCache, clearHistory,
// and removeCookies
...
};
当然,这个功能也可以通过非成员函数来提供,让它调用合适的成员函数就可以了:
void clearBrowser(WebBrowser& wb)
{
wb.clearCache();
wb.clearHistory();
wb.removeCookies();
}
哪种方法才是更好的呢?是成员函数clearEverying还是非成员函数clearBrower?
2. 为什么非成员非友元函数好?
面向对象准则指出数据以及操作数据的函数应该被捆绑到一起,这就表明它建议成员函数是更好的选择。不幸的是,这个建议是不正确的。它曲解了面向对象的含义。面向对象准则指出数据应该尽可能的被封装。违反直觉的是,成员函数clearEverything实际上并没有比非成员函数clearBrower有更好的封装性。并且提供非成员函数能够为web浏览器的相关功能提供更大的包装(packaging)灵活性,相应的,就可以产生更少的编译依赖和更好的可扩展性。因此非成员函数比成员函数在许多方面都要好。明白为什么很重要。
2.1 用非成员非友元能产生更具封装性的类
以封装开始。如果一些东西被封装了,就意味着被隐藏起来了。封装的东西越多,就有更少的客户能看到它们。更少的客户能看到它们就意味着我们有更大的灵活性来进行对它们进行修改,因为我们的修改直接影响的是能看到这些修改的客户。因此封装性越好,就赋予我们更大的能力来对其进行修改。这也是我们将封装摆在第一位的原因:它以一种只影响有限数量的客户的方式为我们修改东西提供了灵活性。
考虑同一个对象相关联的数据。看到这些数据的代码越少(也就是可访问它),数据就被封装的越好,我们就有更大的自由来修改这个对象的数据的一些特征,像数据成员的数量,类型等等。通过确认有多少代码能够看到数据来判断数据的封装性是粗粒度的方法,我们可以计算出能够访问数据的函数的数量,能访问数据的函数越多,封装性越差。
Item 22解释了数据成员应该是private的,因为如果不是,未限定数量的成员函数就能够访问它们。这样就根本没有封装性可言。对于private的数据成员,能够访问它们的函数的数量为所在类的成员函数的数量加上友元函数的数量,因为只有成员函数和友元函数能够访问private成员。考虑在一个成员函数(不仅能访问类的private数据,也能访问private函数,enums,typedef等等)和一个非成员非友元函数(私有的数据和函数都不能访问)之间做一个选择,它们提供了相同的功能,能够产生更大封装性的选择是非成员非友元函数,因为他们没有增加能够访问类私有部分的函数的数量。这就解释了为什么clearBrower(非成员非友元函数)要优于clearEverything:在WebBrowser类中,它产生了更大的封装。
在这点上有两件事情需要注意。第一,这个论述只适用于非成员非友元函数。友元同成员函数对类的私有成员有相同的访问权,因此对封装有相同的影响。从封装的观点来看,不是在成员和非成员函数之间进行选择,而是在成员和非成员非友元函数之间进行选择。(封装当然不是仅有的选择视角,Item 24中解释了在隐式类型转换中,需要在成员和非成员函数之间做出选择。)
第二件需要注意的事情恰恰是因为封装性指明类的函数为非成员函数这个观点,这并不意味着这个函数不能是别的类的成员函数。我们可以将clearBrower声明成一个utility类的静态成员函数。只要它不是WebBrowser的一部分(或者一个友元),它就不会影响WebBrower的private成员的封装性。
2.2 用非成员非友元可以减少编译依赖
在c++中,一个更加自然的方法是使clearBrower成为同WebBrowser有相同命名空间的非成员函数:
namespace WebBrowserStuff {
class WebBrowser { ... };
void clearBrowser(WebBrowser& wb);
...
}
不仅仅是更加自然,因为命名空间不像类,它是可以跨文件的。这是很重要的,因为像clearBrower这样的函数是很便利的函数。既不是成员也不是友元,对WebBrower类没有特殊访问权,因此它不能提供WebBrowser客户没有获取到的其他任何功能。举个例子,如果clearBrower这个函数不存在,客户只好自己调用clearCache,clearHistory,和removeCookies。
像webBrower这样的类可以有大量的便利函数,一些和标签相关,另一些和打印相关还有一些和cookie管理相关等等。通常大多数客户只对其中的一部分有兴趣。没有理由让只对标签便利函数感兴趣的客户编译依赖于cookie相关的便利函数。将它们分开的直接的方法是将它们声明在不同的头文件中。
// header “webbrowser.h” — header for class WebBrowser itself
// as well as “core” WebBrowser-related functionality
namespace WebBrowserStuff {
class WebBrowser { ... };
... // “core” related functionality, e.g.
// non-member functions almost
// all clients need
}
// header “webbrowserbookmarks.h”
namespace WebBrowserStuff {
... // bookmark-related convenience
} // functions
// header “webbrowsercookies.h”
namespace WebBrowserStuff {
... // cookie-related convenience
} // functions
注意标准C++库就是这么组织的。它并没有在std命名空间中将所有东西包含在一个单一的<C++ Stand Library>头文件中,而是有许多头文件(<vector>,<algorithm>,<memory>等等),每个头文件声明了std命名空间中的一部分功能。只使用vector相关功能客户不需要#include <memory>;不需要使用list的客户不必#include <list>。这就允许客户只编译依赖于它们实际用到的部分。(Item 31中讨论了减少编译依赖的其他方法)。当一个功能来源于一个类的成员函数,那么将其分割就是不可能的,因为一个类必须被定义在一个整体中。它不能再分了。
2.2 用非成员非友元可以更好的提供扩展性
将所有的便利函数放在不同的头文件中——但放在一个命名空间中——同样意味着客户可以很容易的对便利函数进行扩展。他们需要做的是向命名空间中添加更多的非成员非友元函数。举个例子,如果一个WebBrower客户决定实现图片下载相关的便利函数,他只需要创建一个头文件,在命名空间WebBrowserStuff中将这些函数进行声明。新函数能像旧的函数一样同它们整合在一起。这也是类不能提供的另外一个性质,因为客户是不能对类定义进行扩展的。当然,客户可以派生出新类,但派生类没有权限访问基类的封装成员(像private成员),这样的“扩展功能”就是二等身份。此外,正如Item 7中解释的,并不是所有类都被设计成基类。
读书笔记 effective c++ Item 23 宁可使用非成员非友元函数函数也不使用成员函数的更多相关文章
- 读书笔记 effective c++ Item 24 如果函数的所有参数都需要类型转换,将其声明成非成员函数
1. 将需要隐式类型转换的函数声明为成员函数会出现问题 使类支持隐式转换是一个坏的想法.当然也有例外的情况,最常见的一个例子就是数值类型.举个例子,如果你设计一个表示有理数的类,允许从整型到有理数的隐 ...
- 读书笔记 effective c++ Item 35 考虑虚函数的替代者
1. 突破思维——不要将思维限定在面向对象方法上 你正在制作一个视频游戏,你正在为游戏中的人物设计一个类继承体系.你的游戏处在农耕时代,人类很容易受伤或者说健康度降低.因此你决定为其提供一个成员函数, ...
- 读书笔记 effective c++ Item 19 像设计类型(type)一样设计
1. 你需要重视类的设计 c++同其他面向对象编程语言一样,定义了一个新的类就相当于定义了一个新的类型(type),因此作为一个c++开发人员,大量时间会被花费在扩张你的类型系统上面.这意味着你不仅仅 ...
- 读书笔记 effective c++ Item 22 将数据成员声明成private
我们首先看一下为什么数据成员不应该是public的,然后我们将会看到应用在public数据成员上的论证同样适用于protected成员.最后够得出结论:数据成员应该是private的. 1. 为什么数 ...
- 读书笔记 effective c++ Item 54 让你自己熟悉包括TR1在内的标准库
1. C++0x的历史渊源 C++标准——也就是定义语言的文档和程序库——在1998被批准.在2003年,一个小的“修复bug”版本被发布.然而标准委员会仍然在继续他们的工作,一个“2.0版本”的C+ ...
- 读书笔记 effective c++ Item 19 像设计类型(type)一样设计类
1. 你需要重视类的设计 c++同其他面向对象编程语言一样,定义了一个新的类就相当于定义了一个新的类型(type),因此作为一个c++开发人员,大量时间会被花费在扩张你的类型系统上面.这意味着你不仅仅 ...
- 读书笔记 effective c++ Item 9 绝不要在构造函数或者析构函数中调用虚函数
关于构造函数的一个违反直觉的行为 我会以重复标题开始:你不应该在构造或者析构的过程中调用虚函数,因为这些调用的结果会和你想的不一样.如果你同时是一个java或者c#程序员,那么请着重注意这个条款,因为 ...
- 读书笔记 effective c++ Item 12 拷贝对象的所有部分
1.默认构造函数介绍 在设计良好的面向对象系统中,会将对象的内部进行封装,只有两个函数可以拷贝对象:这两个函数分别叫做拷贝构造函数和拷贝赋值运算符.我们把这两个函数统一叫做拷贝函数.从Item5中,我 ...
- 读书笔记 effective c++ Item 15 在资源管理类中提供对原生(raw)资源的访问
1.为什么需要访问资源管理类中的原生资源 资源管理类是很奇妙的.它们是防止资源泄漏的堡垒,没有资源泄漏发生是设计良好的系统的一个基本特征.在一个完美的世界中,你需要依赖这样的类来同资源进行交互,绝不 ...
随机推荐
- 1)Java学习笔记:接口和抽象类的异同
Java接口和抽象类很像,他们有哪些相同点和异同点呢,下面我们做一个小结 相同 ① 都不能被实例化,都位于继承树的顶端,用于被实现或者继承 ② 都可以包含抽象方法,实现接口或者继承抽象类的普通子类都必 ...
- call的初步理解
首先说下call的本质是一个函数 模Function.prototype.call = function(context){ // this表示某函数,函数里面的this先被替换成context,然后 ...
- JAVA中用于处理字符串的“三兄弟”
JAVA中用于处理字符串常用的有三个类:java.lang.String.java.lang.StringBuffer.java.lang.StringBuilder,这三者的共同之处都是final类 ...
- Jenkins SSH timeout
问题如下: Started by user carzone Building -test SSH: Connecting from host [jenkins232] SSH: Connecting ...
- PHP分页类,支持自定义样式,中间5页
<?php //namespace Component; /** * 2016-3-27 * @author ankang */ class Page { private $ShowPage; ...
- MySQL5.6生产库自动化安装部署
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://suifu.blog.51cto.com/9167728/1846671 自动化运 ...
- 强行在MFC窗体中渲染Cocos2d-x 3.6
[前言] 把Cocos2dx渲染到另一个应用程序框架中的方法,在2.x时代有很多大神已经实现了,而3.x的做法网上几乎找不着.这两天抽空强行折腾了一下,不敢独享,贴出来供大家参考. [已知存在的问题] ...
- .Net多线程编程—误用点分析
1 共享变量问题 错误写法: 所有的任务可能会共享同一个变量,所以输出结果可能会一样. public static void Error() { ;i<;i++) { Task.Run(() = ...
- LoadRunner相关架构图
LoadRunner概览图: Lr架构图:
- Ubuntu安装搜狗拼音
p { margin-bottom: 0.25cm; direction: ltr; color: rgb(0, 0, 0); line-height: 120% } p.western { font ...