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

1. 为什么数据成员不能是public的?

为什么数据成员不能够是public的?

2.1 一致性

让我们从句法的一致性开始(Item 18)。如果数据成员不是Public的,那么客户访问对象的唯一方法就是通过成员函数。如果所有的公共接口都是函数,客户就不必记住访问一个类的成员时是否使用括号了。这方便了客户的使用。

2.2 对数据成员访问的精确控制

如果一致性没有让你信服,那么使用函数可以使你对数据成员的访问有更加精确的控制呢?如果你将数据成员声明成public的,每个人对其都有读写权限,但是如果你使用函数来对值进行获取(get)或者设置(set),你就可以实现不可访问(no access),只读访问(read only)和读写(read-write)访问。如果你需要,你甚至可以实现只写(write-only)访问:

 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; // no access to this int

 int readOnly; // read-only access to this int

 int readWrite; // read-write access to this int

 int writeOnly; // write-only access to this int

 };

这种细粒度的访问控制是很重要的,因为许多数据成员应该被隐藏起来。很少情况下需要所有的数据成员都有一个getter和一个setter。

2.3 封装

仍然没有说服力?该是使出杀手锏的时候了:封装。如果你通过一个函数来实现对一个数据成员的访问,日后你可能会用计算来替代数据成员,使用你的类的任何客户不会觉察出类的变化。

举个例子,假设你在实现一个应用,自动化设备使用这个应用来记录通过车辆的速度。当每辆车通过的时候,速度被计算出来,然后将结果保存在一个数据集中,这个数据集记录了迄今为止收集的所有速度数据:

 class SpeedDataCollection {

 ...

 public:

 void addValue(int speed); // add a new data value

 double averageSoFar() const; // return average speed

 ...

 };

现在考虑成员函数averageSoFar的实现。一种实现的方法是在类中定义一个数据成员,用来表示迄今为止所有速度数据的平均值。当averageSoFar被调用的时候,它只是返回这个数据成员的值。另外一种方法是在每次调用averageSoFar的时候重新计算平均值,这可以通过检查数据集中的每个数据值来做到。

第一种方法使得每个SpeedDataCollection对象变大,因为你必须为保存平均速度,累积总量以及数据点数量的数据成员分配空间。然而,averageSoFar可以被很高效的实现出来;它只是一个返回平均速度的内联函数(见Item 30)。相反,在请求的时候才计算平均值会使得averageSoFar运行非常缓慢,但是每个SpeedDataCollection对象会比较小。

谁能确定哪个才是更好的呢?在一台内存吃紧的机器上,并且应用中对平均值的需要不是很频繁,每次计算平均值可能会是一个更好的选择。在一个对平均值需求频繁的应用中,速度很重要,但内存充足,你可能更喜欢将平均速度保存为数据成员。这里的重要一点是通过一个成员函数来访问平均值(也就是将其封装起来),你可以在这些不同实现之间来回切换,客户端至多只需要重新编译就可以了。(通过Item31中描述的技术,你甚至可以不用重新编译)

将数据成员隐藏在函数接口后边可以灵活的提供不同种类的实现。举个例子,它可以使下面这些实现变得很简单:当数据成员被读或者写的时候通知其它对象;验证类的不变性和函数的先置和后置条件;在多线程环境中执行同步等等。从其它语言(像Delphi和C#)转到C++的程序员将会识别出来C++的这种功能同其它语言中的“属性”是等同的,但是需要额外加一对括号。

封装比它起初看起来要重要。如果你对客户隐藏你的数据成员(也就是封装它们),你就能够确保类能一直维持不变性,因为只有成员函数能够影响它们。进一步来说,你保留了日后对实现决策进行变动的权利。如果你没有将这些决策隐藏起来,你将会很快发现即使你拥有一个类的源码,但是你修改public成员的能力是及其受限的,因为如果修改public成员,太多的客户代码会被破坏。Public意味这没有封装,更实际的讲,未封装意味这不能变化,特别对被广泛使用的类更是如此。因此对广泛使用的类最需要进行封装,因为它们最能受益于将一个实现替换为一个更好的实现。

2. 为什么数据成员不能是protected的?

上面的论证对于protected数据成员来说是类似的。事实上,它们是完全相同的,虽然一开始看上去不是这样。在论证数据成员不能为public时,句法一致性和细粒度访问控制这两个原因同样适用于protected成员,但是封装呢?protected数据成员不是有比public数据成员更好的封装性么?令人感到吃惊的回答是,它们不是。

Item 23解释了封装性同一些东西发生变化引起的代码可能被破坏的数量成反比。一个数据成员的封装型,同数据成员发生变化引起的代码可能被破坏的数量成反比,举个例子,如果数据成员从类中移除。(可能被一个计算代替,正如在averageSoFar中实现的)。

假设我们有一个public数据成员,我们将其删除。有多少代码会被破坏?所有使用它的客户代码将被破坏,一般情况下这应该是个未知的数量。Public数据成员因此完全没有被封装。但是假设我们有一个protected数据成员,我们将其删除。现在会有多少代码被破坏呢?所有使用它的派生类,同样的,这也是未知数量的代码。Protected数据成员同public数据成员一样也没有被封装,因为在两种情况中,如果数据成员被修改,都有未知数量的客户代码会被破坏。这是违反直观的,但是一个经验丰富的库实现人员会告诉你,这就是真的。一旦你将一个数据成员声明成public或者protected并且客户开始使用它,很难改变数据成员的任何东西。因为一旦修改了,太多的代码会被重新实现,重新测试,重新编辑文档,重新编译。从封装的角度来说,真的只有两种访问级别:private(提供了封装)和其它的(没有提供封装)。

3. 总结

  • 将数据成员声明为private。它为客户提供了对数据句法一致的访问,给予细粒度的访问控制,允许执行类的不变性,为类的作者提供实现的灵活性。
  • Protected没有比public更具封装型。

读书笔记 effective c++ Item 22 将数据成员声明成private的更多相关文章

  1. 读书笔记 effective c++ Item 39 明智而谨慎的使用private继承

    1. private 继承介绍 Item 32表明C++把public继承当作”is-a”关系来对待.考虑一个继承体系,一个类Student public 继承自类Person,如果一个函数的成功调用 ...

  2. 读书笔记 effective c++ Item 41 理解隐式接口和编译期多态

    1. 显示接口和运行时多态 面向对象编程的世界围绕着显式接口和运行时多态.举个例子,考虑下面的类(无意义的类), class Widget { public: Widget(); virtual ~W ...

  3. 读书笔记 effective c++ Item 23 宁可使用非成员非友元函数函数也不使用成员函数

    1. 非成员非友元好还是成员函数好? 想象一个表示web浏览器的类.这样一个类提供了清除下载缓存,清除URL访问历史,从系统中移除所有cookies等接口: class WebBrowser { pu ...

  4. 读书笔记 effective c++ Item 5 了解c++默认生成并调用的函数

    1 编译器会默认生成哪些函数  什么时候空类不再是一个空类?答案是用c++处理的空类.如果你自己不声明,编译器会为你声明它们自己版本的拷贝构造函数,拷贝赋值运算符和析构函数,如果你一个构造函数都没有声 ...

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

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

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

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

  7. 读书笔记 effective c++ Item 6 如果你不想使用编译器自动生成的函数,你需要明确拒绝

    问题描述-阻止对象的拷贝 现实生活中的房产中介卖房子,一个服务于这个中介的软件系统很自然的会有一个表示要被销售的房屋的类: class HomeForSale { ... }; 每个房产中介会立刻指出 ...

  8. 读书笔记 effective c++ Item 28 不要返回指向对象内部数据(internals)的句柄(handles)

    假设你正在操作一个Rectangle类.每个矩形可以通过左上角的点和右下角的点来表示.为了保证一个Rectangle对象尽可能小,你可能决定不把定义矩形范围的点存储在Rectangle类中,而是把它放 ...

  9. 读书笔记 effective c++ Item 18 使接口容易被正确使用,不容易被误用

    1. 什么样的接口才是好的接口 C++中充斥着接口:函数接口,类接口,模板接口.每个接口都是客户同你的代码进行交互的一种方法.假设你正在面对的是一些“讲道理”的人员,这些客户尝试把工作做好,他们希望能 ...

随机推荐

  1. 环信 之 iOS 客户端集成一:导入库

    1. 导入 我采用cocoapod的方式,在project同级目录下创建Podfile,Podfile内容如下: platform :ios, '7.0' pod 'EaseMobSDKFull', ...

  2. ios 清除列表选中状态

    [tableView deselectRowAtIndexPath:indexPath animated:YES];

  3. 兼容ie6及以上和firefox等标准浏览器的表格行滑过时背景色切换的效果

    一.js代码——"tablehover.js" /**      *②.表格单元行滑过时高亮样式动效组件封装      *oop形式封装交互动效类      *组件说明这个组件是为 ...

  4. C语言的指针使用

    今天老师总结了一下指针内容,感觉对理解指针有帮助, 1.大家在使用指针的时候容易忽略掉指针所在的位置.  假如定义一个变量int a=10: int *p: p=&a;    //p中存放变量 ...

  5. 邮件报警shell脚本

    温馨提示  首先要安装postfix 或者 sendmail 等邮件服务器 1.Apache #!/bin/bash #apache.sh nc -w2 localhost 80 if[ $? -ne ...

  6. 利用phpcms-v9站群功能建立多个分站

    用一套CMS软件系统,做多个网站,统一管理,用户可以互通,这就是所谓的站群功能.这对于运营和维护都能节省很多时间,否则要同时调试和维护不同系统会非常吃力. 我在用PHPcms v9做了zhencms1 ...

  7. 部署Sharding分片

    这是一种将海量的数据水平扩展的数据库集群系统,数据分表存储在sharding 的各个节点上,使用者通过简单的配置就可以很方便地构建一个分布式MongoDB 集群. MongoDB 的数据分块称为 ch ...

  8. jQuery wrap wrapAll wrapInner使用

    jQuery wrap wrapAll wrapInner使用 <%@ page language="java" import="java.util.*" ...

  9. MVC下form表单一次上传多种类型的图片(每种类型的图片可以上传多张)

    form表单一次上传多种类型的图片(每种类型的图片可以上传多张) controller中的action方法 public ActionResult UploadImage( )        { in ...

  10. console用法大全

    对于前端开发者来说,在开发过程中需要监控某些表达式或变量的值的时候,用 debugger 会显得过于笨重,取而代之则是会将值输出到控制台上方便调试.最常用的语句就是console.log(expres ...