章节回顾:

《Effective C++》第1章 让自己习惯C++-读书笔记

《Effective C++》第2章 构造/析构/赋值运算(1)-读书笔记

《Effective C++》第2章 构造/析构/赋值运算(2)-读书笔记

《Effective C++》第3章 资源管理(1)-读书笔记

《Effective C++》第3章 资源管理(2)-读书笔记

《Effective C++》第4章 设计与声明(1)-读书笔记

《Effective C++》第4章 设计与声明(2)-读书笔记

《Effective C++》第5章 实现-读书笔记

《Effective C++》第8章 定制new和delete-读书笔记


条款26:尽可能延后变量定义式的出现时间

你定义了一个类类型的变量,那么就要耗费一个构造函数和析构函数。如果你最终不使用这个变量,就应该避免这些耗费。

你可能会怀疑:怎么可能定义一个变量而不去使用呢?考虑下面的代码:

  1. std::string encryptPassword(const std::string& password)
  2. {
  3. using namespace std;
  4. string encrypted;
  5.  
  6. if (password.length() < MinimumPasswordLength)
  7. {
  8. throw logic_error("Password is too short")
  9. }
  10.  
  11. ...
  12. return encrypted;
  13. }

先不去考虑代码具体含义。如果if语句为true,就会抛出异常,这个encrypted对象仍然需要耗费一个构造函数和一个析构函数。所以最好延后encrypted的定义式,直到确实需要它。

  1. std::string encryptPassword(const std::string& password)
  2. {
  3. using namespace std;
  4.  
  5. if (password.length() < MinimumPasswordLength)
  6. {
  7. throw logic_error("Password is too short")
  8. }
  9.  
  10. string encrypted; //放在了后面
  11. ...
  12. return encrypted;
  13. }

这段代码不够秾纤合度(不懂这个词)。因为encrypted对象调用的是默认构造函数,后面几乎一定会对它重新赋值。举例如下:

  1. void encrypt(std::string& s);
  2. std::string encryptPassword(const std::string& password)
  3. {
  4. ...
  5. string encrypted; //放在了后面,考虑到使用时才定义
  6.  
  7. encrypted = password; //重新赋值
  8.  
  9. encrypt(encrypted);
  10.  
  11. return encrypted;
  12. }

更好的做法是跳过无意义的default构造函数:

  1. std::string encryptPassword(const std::string& password)
  2. {
  3. ...
  4. string encrypted(password); //拷贝构造函数
  5.  
  6. encrypt(encrypted);
  7.  
  8. return encrypted;
  9. }

所以,“尽可能延后”的真正意义是:你不仅要尽可能延后变量的定义直到要使用它,还应该延后这个变量的定义直到给它初值。这样可以避免没有必要的构造和析构对象以及没有意义的default构造函数。

还有一种情形出现在循环里面,分下下面两种做法A、B哪个更好:

  1. //做法A
  2. Widget w;
  3. for (int i = ; i < n; ++i)
  4. {
  5. w = 取决于i的某个值;
  6. }
  7.  
  8. //做法B
  9. for (int i = ; i < n; ++i)
  10. {
  11. Widget w = 取决于i的某个值;
  12. }

做法A的成本:1个构造函数、1个析构函数和n个赋值;做法B的成本:n个构造函数和n个析构函数。

如果赋值成本低于1个构造+1个析构,则做法A效率高一点,否则B的做法好。另外做法A造成Widget对象作用域扩大。所以,给出的建议是:除非你明确知道几个操作的成本,否则做法B是比较好的。

请记住:尽可能延后变量定义式的出现。这样做可增加程序的清晰度并改善程序效率。


条款27:尽量少做转型动作

C风格的转型(旧式转型)如下:

  1. (T) expression; //两者含义相同
  2. T(expression)

C++提供的4种新式转型如下:

  1. const_cast<T>(expression)
  2. dynamic_cast<T>(expression)
  3. reinterpret_cast<T>(expression)
  4. static_cast<T>(expression)

一般来说新式转型比较好。可能旧式转型比较常用的地方是调用explicit构造函数传递一个对象给函数时。举例如下:

  1. class Widget
  2. {
  3. public:
  4. explicit Widget(int size);
  5. };
  6. void doSomeWork(const Widget& w);
  7.  
  8. doSomeWork(Widget()); //C的函数风格
  9.  
  10. doSomeWork(static_cast<Widget>()); //C++新风格

任何一个类型转换,无论是通过转型操作进行的显式转换或通过编译器进行的隐式转换,往往会导致编译器产生运行期执行的代码。

下面有个转型代码比较有迷惑:

  1. class Window
  2. {
  3. public:
  4. Window(int n = ) : m(n) {}
  5.  
  6. virtual void onResize()
  7. {
  8. m = ;
  9. }
  10.  
  11. int m;
  12. };
  13.  
  14. class SpecialWindow : public Window
  15. {
  16. public:
  17. virtual void onResize()
  18. {
  19. static_cast<Window>(*this).onResize();
  20. }
  21. };
  22.  
  23. int main()
  24. {
  25. SpecialWindow w1;
  26. cout << w1.m << endl; //输出0
  27.  
  28. w1.onResize();
  29. cout << w1.m << endl; //输出0
  30.  
  31. return ;
  32. }

两份输出都是0。不要怀疑,static_cast<Window>(*this).onResize();确实调用了class Window的onResize()函数,但关键是转型的结果是(*this)的一个副本,而不是对象本身。

如果你仍然需要调用class Window版本的onResize()函数,就要拿掉转型。

  1. class SpecialWindow : public Window
  2. {
  3. public:
  4. virtual void onResize()
  5. {
  6. Window::onResize();
  7. }
  8. };

dynamic_cast的成本很高,之所以需要它的一个原因是:在一个你认定为derived class对象身上执行derived class函数,但你只有一个指向base的指针或引用。

请记住:

(1)如果可以,尽量避免转型,特别是在注重效率的代码中避免dynamic_cast。如果有个设计需要转型动作,试着发展无需转型的替代设计。

(2)如果转型是必要的,试着将它隐藏于某个函数背后,客户随后可以调用该函数,而不需将转型放在自己的代码内。

(3)优先使用C++风格的转型,因为它很容易被辨识出来并且有分类。


条款28:避免返回handles指向对象内部成分

handles包括指针、引用和迭代器。直接用例子说明:

  1. class Point //表示一个“点”
  2. {
  3. public:
  4. Point(int x, int y);
  5. ...
  6. void setX(int newVal);
  7. void setY(int newVal);
  8. }
  9.  
  10. struct RectData
  11. {
  12. Point ulhc; //表示左上角坐标
  13. Point lrhc; //表示右下角坐标
  14. };
  15.  
  16. class Rectangle
  17. {
  18. public:
  19. Point& upperLeft() const { return pData->ulhc; } //返回左上角坐标
  20. Point& lowerRight() const { return pData->lrhc; } //返回右下角坐标
  21. };

Rectangle类设计两个成员函数upperLeft(),lowerRight()返回左上角和右下角坐标是必要的。但这两个函数都是const的,说明它的目的只是给用户查看,并不是让用户去修改这些坐标。但是客户这样做:

  1. Point coord1(, );
  2. Point coord2(, );
  3.  
  4. const Rectangle rec(coord1, coord2);
  5. rec.upperLeft().setX(); //左上角坐标变为(50,0)

确实改变了坐标值,尽管point还是private数据。这给我们的启示是:成员变量的封装性最多只等于返回其reference函数的访问级别。虽然point是private的,但实际效果却是public的。

修改版本也很简单:

  1. class Rectangle
  2. {
  3. public:
  4. const Point& upperLeft() const { return pData->ulhc; } //返回左上角坐标的const
  5. const Point& lowerRight() const { return pData->lrhc; } //返回右下角坐标的const
  6. };

另外一点handles指向的东西返回后可能不再存在。举例说明:

  1. class GUIObject { ... };
  2. const Rectangle boundingBox(const GUIObject& obj);
  3.  
  4. //客户如下这样调用
  5. GUIObject *pgo;
  6. const Point *pUpperLeft = &(boundingBox(*pgo).upperLeft());

boundingBox(*pgo)的调用将产生一个临时的Rectangle对象,在此对象上调用upperLeft()返回的是临时对象的左上角坐标,然后这个临时对象析构,这样pUpperLeft就是指向一个不存在的东西。

请记住:避免返回handles(包括引用、指针和迭代器)指向内部对象。遵守这个条款可增加封装性、帮助const成员函数的行为像个const,并降低发生handles指向不存在东西的可能性。

《Effective C++》第5章 实现-读书笔记的更多相关文章

  1. 《TCP/IP详解卷1:协议》第1章 概述-读书笔记

    章节回顾: <TCP/IP详解卷1:协议>第1章 概述-读书笔记 <TCP/IP详解卷1:协议>第2章 链路层-读书笔记 <TCP/IP详解卷1:协议>第3章 IP ...

  2. 《Effective Java中文版第二版》读书笔记

    说明 这里是阅读<Effective Java中文版第二版>的读书笔记,这里会记录一些个人感觉稍微有些重要的内容,方便以后查阅,可能会因为个人实力原因导致理解有误,若有发现欢迎指出.一些个 ...

  3. Pro Git 第一章 起步 读书笔记

    Pro Git 笔记 第1章 起步 1.文件的三种状态. 已提交:文件已经保存在本地数据库中了.(commit) 已修改:修改了某个文件,但还没有提交保存.(vim) 已暂存:已经把已修改的文件放在下 ...

  4. unix network programming(3rd)Vol.1 [第2~5章]《读书笔记系列》

    13~22章 重要 第2章 传输层: TCP/ UDP / STCP (Stream Control Transmission Protocol) TCP 可靠,有重传机制,SYN队列号 UDP 不可 ...

  5. unix network programming(3rd)Vol.1 [第13~15章]《读书笔记系列》

    第13章 守护进程和inetd 超级服务器 syslog() daemon_init() setuid() setgid() 第14章 高级IO 标准I/O函数库,支持3种缓冲 缓冲(读写存储设备(硬 ...

  6. unix network programming(3rd)Vol.1 [第1章]《读书笔记系列》

    文章最开头介绍了 获取时间的C/S 模型的代码, 还用了实现了IPV6的版本 unix 介绍了errno值,以及在多进程/多线程中的问题 多线程中不用全局errno,而是用返回值 处理error 详细 ...

  7. Java核心技术卷一基础知识-第14章-多线程-读书笔记

    第 14 章 多线程 本章内容: * 什么是线程 * 中断线程 * 线程状态 * 线程属性 * 同步 * 阻塞队列 * 线程安全的集合 * Collable与Future * 执行器 * 同步器 * ...

  8. Java核心技术卷一基础技术-第13章-集合-读书笔记

    第13章 集合 本章内容: * 集合接口 * 具体的集合 * 集合框架 * 算法 * 遗留的集合 13.1 集合接口 Enumeration接口提供了一种用于访问任意容器中各个元素的抽象机制. 13. ...

  9. Java核心技术卷一基础知识-第8章-事件处理-读书笔记

    第8章 事件处理 本章内容: * 事件处理基础 * 动作 * 鼠标事件 * AWT事件继承层次 8.1 事件处理基础 在AWT所知的事件范围内,完全可以控制事件从事件源(event source)例如 ...

随机推荐

  1. 【c++ primer, 5e】设计Sales_data类 & 定义改进的Sales_data类

    [设计Sales_data类] 1.考虑Sales_data类应该有那些接口. isbn.combine.add.read.print... 2.考虑如何使用这些接口. Sales_data tota ...

  2. SpringMvc接受特殊符号参数被转义

    WEB开发时,在前端通过get / post 方法传递参数的时候  如果实参附带特殊符号,后端接收到的值中特殊符号就会被转义 例如该请求: http://localhost:10001/demo/in ...

  3. jQuery判断元素是否显示 是否隐藏

    var node=$('#id'); 第一种写法 if(node.is(':hidden')){ //如果node是隐藏的则显示node元素,否则隐藏 node.show(); }else{ node ...

  4. linux 分区格式化

    要对一个u盘进行分区 windows上直接格式化就行了,但是我的u盘 由于之前做成的系统把u盘分成三个分区,windows只能格式化第一个分区其他两个分区只能看着,理论上windows上也有dd之类的 ...

  5. vsftpd基于mysql的认证方式

    安装epel源: cd /etc/yum.repos.d wget http://mirrors.neusoft.edu.cn/epel/epel-release-latest-6.noarch.rp ...

  6. [HAOI2017模拟]囚人的旋律

    没有传送门辣. 神奇的DP题. 首先看到这道题第一眼应该想到正解不是在图上搞,肯定要把原图转化成序列. 根据逆序对的性质.每个点和标号大于他的点连边的点,其权值必定要小于该点,而没和他连边的且标号大于 ...

  7. Spring MVC工作流程图

    图一   图二    Spring工作流程描述       1. 用户向服务器发送请求,请求被Spring 前端控制Servelt DispatcherServlet捕获:       2. Disp ...

  8. [BZOJ1131]Sta

    Description 给出一个N个点的树,找出一个点来,以这个点为根的树时,所有点的深度之和最大 Input 给出一个数字N,代表有N个点.N<=1000000 下面N-1条边. Output ...

  9. jq定时器

    1.$(function(){ setInterval (showTime, 2000); function showTime(){ var today = new Date(); alert(&qu ...

  10. Java高级面试题

    一.基础知识: 1)集合类:List和Set比较,各自的子类比较(ArrayList,Vector,LinkedList:HashSet,TreeSet): 2)HashMap的底层实现,之后会问Co ...