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 宁可使用非成员非友元函数函数也不使用成员函数的更多相关文章

  1. 读书笔记 effective c++ Item 24 如果函数的所有参数都需要类型转换,将其声明成非成员函数

    1. 将需要隐式类型转换的函数声明为成员函数会出现问题 使类支持隐式转换是一个坏的想法.当然也有例外的情况,最常见的一个例子就是数值类型.举个例子,如果你设计一个表示有理数的类,允许从整型到有理数的隐 ...

  2. 读书笔记 effective c++ Item 35 考虑虚函数的替代者

    1. 突破思维——不要将思维限定在面向对象方法上 你正在制作一个视频游戏,你正在为游戏中的人物设计一个类继承体系.你的游戏处在农耕时代,人类很容易受伤或者说健康度降低.因此你决定为其提供一个成员函数, ...

  3. 读书笔记 effective c++ Item 19 像设计类型(type)一样设计

    1. 你需要重视类的设计 c++同其他面向对象编程语言一样,定义了一个新的类就相当于定义了一个新的类型(type),因此作为一个c++开发人员,大量时间会被花费在扩张你的类型系统上面.这意味着你不仅仅 ...

  4. 读书笔记 effective c++ Item 22 将数据成员声明成private

    我们首先看一下为什么数据成员不应该是public的,然后我们将会看到应用在public数据成员上的论证同样适用于protected成员.最后够得出结论:数据成员应该是private的. 1. 为什么数 ...

  5. 读书笔记 effective c++ Item 54 让你自己熟悉包括TR1在内的标准库

    1. C++0x的历史渊源 C++标准——也就是定义语言的文档和程序库——在1998被批准.在2003年,一个小的“修复bug”版本被发布.然而标准委员会仍然在继续他们的工作,一个“2.0版本”的C+ ...

  6. 读书笔记 effective c++ Item 19 像设计类型(type)一样设计类

    1. 你需要重视类的设计 c++同其他面向对象编程语言一样,定义了一个新的类就相当于定义了一个新的类型(type),因此作为一个c++开发人员,大量时间会被花费在扩张你的类型系统上面.这意味着你不仅仅 ...

  7. 读书笔记 effective c++ Item 9 绝不要在构造函数或者析构函数中调用虚函数

    关于构造函数的一个违反直觉的行为 我会以重复标题开始:你不应该在构造或者析构的过程中调用虚函数,因为这些调用的结果会和你想的不一样.如果你同时是一个java或者c#程序员,那么请着重注意这个条款,因为 ...

  8. 读书笔记 effective c++ Item 12 拷贝对象的所有部分

    1.默认构造函数介绍 在设计良好的面向对象系统中,会将对象的内部进行封装,只有两个函数可以拷贝对象:这两个函数分别叫做拷贝构造函数和拷贝赋值运算符.我们把这两个函数统一叫做拷贝函数.从Item5中,我 ...

  9. 读书笔记 effective c++ Item 15 在资源管理类中提供对原生(raw)资源的访问

    1.为什么需要访问资源管理类中的原生资源  资源管理类是很奇妙的.它们是防止资源泄漏的堡垒,没有资源泄漏发生是设计良好的系统的一个基本特征.在一个完美的世界中,你需要依赖这样的类来同资源进行交互,绝不 ...

随机推荐

  1. Struts2---Result(传统Web应用程序与Ajax应用程序的异同)

    看了很久的struts,在视频和书的引导下,慢慢明白了点,推荐:<struts 2 in action>和马士兵的视频 今天看结果这块时,由于还没有学过ajax等,不太明白,但是必须弄懂嗒 ...

  2. .net中的4种事务总结

    在 一个MIS系统中,没有用事务那就绝对是有问题的,要么就只有一种情况:你的系统实在是太小了,业务业务逻辑有只要一步执行就可以完成了.因此掌握事务处 理的方法是很重要,进我的归类在.net中大致有以下 ...

  3. windy数(bzoj 1227)

    Description windy定义了一种windy数.不含前导零且相邻两个数字之差至少为2的正整数被称为windy数. windy想知道,在A和B之间,包括A和B,总共有多少个windy数? In ...

  4. UVa 10427 - Naughty Sleepy Boys

    题目大意:从1开始往后写数字,构成一个如下的字符串 123456789101112... .求第n位的数字是多少. 找规律,按数字的位数可以构建一个类似杨辉三角的东西,求出第n位是哪个数的第几位即可. ...

  5. css3 翻牌效果

    <!DOCTYPE html> <head> <meta http-equiv="Content-Type" content="text/h ...

  6. shell 远程备份日志

    #!/bin/bash #Function:自动备份给定列表中的目录或文件,并且可以保留N天备份的档案. #可备份至远程主机指定的目录下,但需本机能免密码登录到远程主机,用到ssh-keygen #该 ...

  7. js原生设计模式——9外观模式封装2(小型代码库YJ)

    <script type="text/javascript">    //小型代码库YJ封装    var YJ = {        //根据id获取元素       ...

  8. ViewFlipper的功能与用法

    ViewFlipper组件继承了ViewAnimator,它可调用addView(View v)添加多个组件,一旦向ViewFlipper中添加了多个组件之后,ViewFlipper可使用动画控制多个 ...

  9. Velocity教程

    Velocity 语法(转) 一.基本语法 1."#"用来标识Velocity的脚本语句,包括#set.#if .#else.#end.#foreach.#end.#iinclud ...

  10. Flex组件的生命周期

    组件实例化生命周期描述了用组件类创建组件对象时所发生的一系列步骤,作为生命周期的一部分,flex自动调用组件的的方法,发出事件,并使组件可见. 下面例子用as创建一个btn控件,并将其加入容器中 va ...