《Effective C++》资源管理:条款22

或许一致性不是令你信服的理由。还有一个理由:使用函数可以让你对成员变量的处理有更加精确的控制。如果成员变量为public,那么每个人都能读和写,但是如果通过函数读或写其值,那么就能实现“不准访问”、“只读访问”以及“读写访问”,甚至实现“惟写访问”,这个性质有点像C#中的get、set。class AccessLevels{public:……int getReadOnly() const {return readOnly;}void setReadWrite(int value){readWrite=value;}int getReadWrite()const{return readWrite;}void setWriteOnly(int value){writeOnly=value;}private:int noAccess;//无任何访问动作int readOnly;//只能读int readWrite;//能读能写int writeOnly;//只能写};如果上述理由还不够,那么还有一个更重要的理由:封装。如果通过函数访问成员变量,日后可以用某个计算替换这个变量,这时class的客户却不知道内部实现已经变化。例如,你正在写一个自动测速程序,当汽车通过,其速度便被填入到一个速度收集器内:class SpeedDataCollection{……public:void addValue(int speed);//添加一笔新数据double averageSoFar() const;//返回平均速度……};现在考虑怎么实现函数averageSoFar。一种做法是在class内设计一个变量,记录至今以来所有速度 的平均值;当averageSoFar被调用,只需要返回那个成员变量就好。另一种做法是让averageSoFar每次被调用时重新计算平均值,这个函数有权限读取收集器内的每一笔速度值。上述第一种做法(随时保持平均值)会使每一个SpeedDataCollection对象变大,因为必须为用来存放目前平均值、累计总量、数据点数的每一个成员变量分配空间;但是这会使averageSoFar十分高效,它可以只是一个返回目前平均值的inline函数(条款30)。第二种做法,“每次被问询才计算平均值”会使得averageSoFar执行较慢,但是这时SpeedDataCollection对象占用空间比较小。具体哪种做法比较好,要视具体情况而定。在内存吃紧的机器上(例如嵌入式设备),或者在不需要常常计算平均值的应用中,第二种做法比较合适。但是在一个频繁需要平均值的应用程序中,如果反应速度非常重要,内存不是考虑因素,这时候第一种做法 更好。上面这两种实现都是通过函数来访问平均值(即封装了它),你可以替换不同的实现方式,客户最多只需要重新编译。(如果遵循条款31,甚至你都不需要重新编译)将成员变量隐藏在函数接口背后,可以为“所有可能的实现”提供弹性。例如这使得成员变量被读或被写时轻松通知其他对象、可以验证class的约束条件以及函数的前提和事后状态、可以在多线程环境中执行同步控制……等等。封装的重要性或许比你想象中重要。如果你对客户隐藏成员变量(封装它们),可以确保class约束条件获得维护、保留了日后变更实现的权利。如果不封装,日后更改public事物的能力是极端收到束缚,因为修改public变量会影响太多客户代码。protected成员的封装貌似高于public,但是事实并非如此,修改protected成员变量,多少derived类需要修改或多少使用derived对象的客户代码需要修改。条款23中,将会看到“某些东西的封装性”与“当期内容改变时可能造成的代码破坏量”成正比。一旦成员变量声明为public或protected,就能难改变那个成员变量所涉及的一切。因为太多代码需要重写、重新测试、重新编写文档、重新编译。从封装角度看,只有两种访问权限:private(封装)和其他(不封装)。总结:1、将成员变量声明为private。这可以赋予客户访问数据的一致性、可细微划分访问控制、允许约束条件获得保证,并提供class作者以充分弹性实现。2、protected并不比public更具有封装性。条款23:宁以non-member、non-friend替换member函数

这个条款讲解成员函数和友函数的区别。

考虑一个class用来清除浏览器的一些记录,这个class中有清除告诉缓存区的函数,有清除访问过URLs的函数,还有清除cookies的函数:

class WebBrowser{public:……void clearCash();void clearHistory();void removeCookies();……};一般情况下,需要同时执行这三个动作,因此WebBrowser可以提供这样一个函数:class WebBrowser{public:……void clearEverything(){clearCash();clearHistory();removeCookies();}……};另一种做法是用一个non-member函数调用适当的member函数void clearBrowser(WebBrowser& wb){wb.clearCash();wb.clearHistory();wb.removeCookies();};上面两种做法,哪种比较好呢?答案是non-member函数比较好。

面向对象思想要求,数据尽可能被封装,member函数clearEverything带来的封装性比non-member函数clearBrowser低。提供non-member函数,对class相关机能有较大包裹弹性(packaging flexibility),因此带来了较低的编译相依度,增加了class的可延展性。

封装意味着不可见。愈多东西被封装,欲少人可以看到它,我们就有愈大的弹性去改变它。愈少代码可以看到数据(访问数据),愈多数据可被封装,我们就更有自由来改变对象数据。愈多函数可以访问它,数据的封装性就愈低。

条款22有讲到,成员变量应该是private,否则就有无限多函数可以访问它,毫无封装可言。能访问private成员变量的函数只有class的member函数、friend函数而已。在一个member函数和一个non-member、non-friend函数之间做抉择,如果两者提供相同的机能,显然后者提供了更大的封装,这个就是上面选择clearBrowser函数的原因。

在封装这点上,需要注意两点。1、这个论述只适用于non-member、non-friend函数。2、因为封装,让函数成为class的non-member函数,但这并不意味着它不可以是另一个class的member函数。

在C++中,实现上述功能,比较自然的做法是把clearBrowser函数和WebBrowser类放到一个命名空间内:

namespace WebBrowserStuff{class WebBrowser{……};void clearBrowser(WebBrowser& we);……}这不仅仅是看起来整齐。namespace可以跨越多个源码文件,class不能。像clearBrowser这样的函数只是为了提供便利,它是non-member、non-friend,没有对WebBrowser的特殊访问权力。一个像WebBrowser这样的class可能拥有大量便利函数,例如某些与书签相关,某些与打印有关,某些与cookies相关……。通常客户使用是时只是对其中一些感兴趣。在编码时通常分离它们:将书签相关便利函数声明于一个头文件,将cookie相关函数声明于另一个头文件,再将打印相关函数声明到第三个头文件……。//头文件webbrowser.h,这个头文件针对class WebBrowser自身及WebBrowser核心机能namespace WebBrowserStuff{class WebBrowser{……};//核心机能……//non-member函数}//头文件webbrowserbookmarks.hnamespace WebBrowserStuff{……//与书签相关的便利函数}//头文件webbrowsercookies.hnamespace WebBrowserStuff{……//与cookie相关的便利函数}这也正是C++标准库的组织方式。标准库有数十个头文件(<vector>,<algorithm>,<memroy>等等),,每个头文件声明std的某些机能。如果客户想使用vector相关机能,只需要#include<vector>即可。这也允许客户只对他们所用的那一小部分形成编译相依(条款31,其中讨论降低编译依赖性的其他做法)。那段岁月,无论从何种角度读你,你都完美无缺,

《Effective C++》资源管理:条款22

相关文章:

你感兴趣的文章:

标签云: