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

简介: 我们首先看一下为什么数据成员不应该是public的,然后我们将会看到应用在public数据成员上的论证同样适用于protected成员。最后够得出结论:数据成员应该是private的。 1. 为什么数据成员不能是public的? 为什么数据成员不能够是public的? 2.1 一致性 让我们从句法的一致性开始(Item 18)。

我们首先看一下为什么数据成员不应该是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)访问:

 1 class AccessLevels {
 2 
 3 public:
 4 
 5 ...
 6 
 7 int getReadOnly() const { return readOnly; }
 8 
 9 void setReadWrite(int value) { readWrite = value; }
10 
11 int getReadWrite() const { return readWrite; }
12 
13 void setWriteOnly(int value) { writeOnly = value; }
14 
15 private:
16 
17 int noAccess; // no access to this int
18 
19 int readOnly; // read-only access to this int
20 
21 int readWrite; // read-write access to this int
22 
23 int writeOnly; // write-only access to this int
24 
25 };

 

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

2.3 封装

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

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

 1 class SpeedDataCollection {
 2 
 3 ...
 4 
 5 public:
 6 
 7 void addValue(int speed); // add a new data value
 8 
 9 double averageSoFar() const; // return average speed
10 
11 ...
12 
13 };

 

现在考虑成员函数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更具封装型。


作者: HarlanC

博客地址: http://www.cnblogs.com/harlanc/
个人博客: http://www.harlancn.me/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出, 原文链接

如果觉的博主写的可以,收到您的赞会是很大的动力,如果您觉的不好,您可以投反对票,但麻烦您留言写下问题在哪里,这样才能共同进步。谢谢!

目录
相关文章
|
7月前
|
存储 监控 算法
基于 C++ 哈希表算法实现局域网监控电脑屏幕的数据加速机制研究
企业网络安全与办公管理需求日益复杂的学术语境下,局域网监控电脑屏幕作为保障信息安全、规范员工操作的重要手段,已然成为网络安全领域的关键研究对象。其作用类似网络空间中的 “电子眼”,实时捕获每台电脑屏幕上的操作动态。然而,面对海量监控数据,实现高效数据存储与快速检索,已成为提升监控系统性能的核心挑战。本文聚焦于 C++ 语言中的哈希表算法,深入探究其如何成为局域网监控电脑屏幕数据处理的 “加速引擎”,并通过详尽的代码示例,展现其强大功能与应用价值。
180 2
|
8月前
|
存储 C++
UE5 C++:自定义Http节点获取Header数据
综上,通过为UE5创建一个自定义HTTP请求类并覆盖GetResult方法,就能成功地从HTTP响应的Header数据中提取信息。在项目中使用自定义类,不仅可以方便地访问响应头数据,也可随时使用这些信息。希望这种方法可以为你的开发过程带来便利和效益。
315 35
|
9月前
|
安全 C++
【c++】继承(继承的定义格式、赋值兼容转换、多继承、派生类默认成员函数规则、继承与友元、继承与静态成员)
本文深入探讨了C++中的继承机制,作为面向对象编程(OOP)的核心特性之一。继承通过允许派生类扩展基类的属性和方法,极大促进了代码复用,增强了代码的可维护性和可扩展性。文章详细介绍了继承的基本概念、定义格式、继承方式(public、protected、private)、赋值兼容转换、作用域问题、默认成员函数规则、继承与友元、静态成员、多继承及菱形继承问题,并对比了继承与组合的优缺点。最后总结指出,虽然继承提高了代码灵活性和复用率,但也带来了耦合度高的问题,建议在“has-a”和“is-a”关系同时存在时优先使用组合。
496 6
|
10月前
|
算法 Serverless 数据处理
从集思录可转债数据探秘:Python与C++实现的移动平均算法应用
本文探讨了如何利用移动平均算法分析集思录提供的可转债数据,帮助投资者把握价格趋势。通过Python和C++两种编程语言实现简单移动平均(SMA),展示了数据处理的具体方法。Python代码借助`pandas`库轻松计算5日SMA,而C++代码则通过高效的数据处理展示了SMA的计算过程。集思录平台提供了详尽且及时的可转债数据,助力投资者结合算法与社区讨论,做出更明智的投资决策。掌握这些工具和技术,有助于在复杂多变的金融市场中挖掘更多价值。
377 12
|
10月前
|
存储 监控 算法
公司监控上网软件架构:基于 C++ 链表算法的数据关联机制探讨
在数字化办公时代,公司监控上网软件成为企业管理网络资源和保障信息安全的关键工具。本文深入剖析C++中的链表数据结构及其在该软件中的应用。链表通过节点存储网络访问记录,具备高效插入、删除操作及节省内存的优势,助力企业实时追踪员工上网行为,提升运营效率并降低安全风险。示例代码展示了如何用C++实现链表记录上网行为,并模拟发送至服务器。链表为公司监控上网软件提供了灵活高效的数据管理方式,但实际开发还需考虑安全性、隐私保护等多方面因素。
214 0
公司监控上网软件架构:基于 C++ 链表算法的数据关联机制探讨
|
11月前
|
存储 算法 搜索推荐
【C++面向对象——群体类和群体数据的组织】实现含排序功能的数组类(头歌实践教学平台习题)【合集】
1. **相关排序和查找算法的原理**:介绍直接插入排序、直接选择排序、冒泡排序和顺序查找的基本原理及其实现代码。 2. **C++ 类与成员函数的定义**:讲解如何定义`Array`类,包括类的声明和实现,以及成员函数的定义与调用。 3. **数组作为类的成员变量的处理**:探讨内存管理和正确访问数组元素的方法,确保在类中正确使用动态分配的数组。 4. **函数参数传递与返回值处理**:解释排序和查找函数的参数传递方式及返回值处理,确保函数功能正确实现。 通过掌握这些知识,可以顺利地将排序和查找算法封装到`Array`类中,并进行测试验证。编程要求是在右侧编辑器补充代码以实现三种排序算法
270 5
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
340 5
|
编译器 C语言 C++
C++入门3——类与对象2-2(类的6个默认成员函数)
C++入门3——类与对象2-2(类的6个默认成员函数)
144 3
|
存储 编译器 数据安全/隐私保护
【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解2
【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解
184 3
|
存储 编译器 C++
C++入门3——类与对象2-1(类的6个默认成员函数)
C++入门3——类与对象2-1(类的6个默认成员函数)
200 1