总结: 囫囵吞枣地看完了EffectiveC++这本书,收获不少。收获主要是以下几个方面:
- 编程注意细节
- 提高程序效率的细节比如
const
- is-a 和 has-a的区别
- pure-virtual、impure-virtual和non-virtual之间继承该如何处理
- 继承中隐藏基类的成员等
- 提高程序效率的细节比如
- 设计方法
- 什么时候该继承(is-a),什么时候该以对象作为data member(is-inplemented-in-terms-of);
- Template method、 Strategy, 抽象工厂等设计模式
- 泛型编程与模板编程
- 异常安全性
- 异常处理
- copy-swap技术
由于没有太多的实战经历,所以有些东西并不是很懂。 所幸在看书过程中,做过一些笔记整理,其中 大部分内容摘抄自书上,一小部分加上自己的一点点理解,方便日后回顾。
02: 对于单纯常量,最好以
const
对象或enums
替换#define
03: 尽可能使用
const
mutable
关键字表征const
成员函数可以修改mutable
成员
04: 为内置对象进行手工初始化,C++并不保证初始化他们;
05: 在类中,如果有引用 或const对象,记得要自己编写构造函数,拷贝构造函数等,因为编译器自己生成的相关函数很大可能达不到要求。
06: 为了驳回编译器自动提供的功能(构造函数,拷贝等),可以将相应的member function declared private,并且不予实现; 或者使用像Uncopyable这样的base class;
07: 当类作为基类的时候,有可能表现出多态的时候,应将虚构函数设计成virtual
09: 绝不在构造函数和析构函数中调用virtual function;
如果在base class 调用了virtual function,在派生类的构造函数初始化基类的时候,实际上还是调用的是base class的function,并没有调用dervied class的内容(此时derived class 并没有完成初始化)10: 令 operator= 返回一个referece to *this (方便连续赋值 如 a = b = c;);
11: 在operator=中处理“自我赋值”copy and swap 技术实现自我赋值;
class Widgt {
void swap(Widgt& rhs);// 交换*this 和 rhs的数据
}
Widget& Widget::operator=(const Widget& rhs)
{
Widget tmp(rhs);
swap(rhs);
return *this;
}
12: 复制对象的时候务必记住每一个成员
copying函数应该确保复制“对象内的所有成员变量” 以及“所有base Class 的成分”13: 以对象管理资源 记得用智能指针管理资源;
14: 在资源管理类中心小心copying行为
- 当资源管理类需要copy的时候 需要从以下几个方面考虑
- 禁止复制(将拷贝构造函数,以及拷贝赋值函数 设置于private有点类似于unique_ptr<>)
- 对底层资源使用“引用计数法” 模仿“shared_ptr”
- 复制底部资源(深拷贝,每一份对象都有自己的heap内存)
- 转移资源的管理权(有点像 auto_ptr )
- 当资源管理类需要copy的时候 需要从以下几个方面考虑
15: 资源管理类中需要提供对原始资源的访问
- 智能指针类可以隐式转化为底部的原始指针,便于访问底部的成员
- 可以有显式转换(较为安全)和隐式转换(对客户比较安全)
16: 成对使用new 和delete 尤其注意在利用typedef中声明新的类型名时(尽量少用typedef声明数组类型)
- 17: 以独立语句将new出来的对象置入智能指针
Design and Declarations
- 18: make interfaces easy to use correctly and hard to use incorrectly
- 令自己的types和内置types的行为一致
- 尽量保持接口一致性 如STL容器的接口
- 19: Treat class design as type design
- type的对象应该如何该创建和销毁;
- 对象的初始化和赋值该有什么样的区别
- 新type的对象如果pass by value 意味着什么
- 新type的合法值 ?哪些赋值是合法的
- 是否需要配合某个继承图系(inheritance graph)吗?
- 什么样的类型转换?
- 什么样的操作符和函数对此新type而言是合理的?(设计接口)
- 什么样的标准函数应该被驳回?(设计为private)
- 谁该取用新type的成员? (设计为public)
- 20: 以pass-by-reference to const 替换pass-by-value
- 前者比较高效,同时可以避免切割问题;
- 对于内置类型、STL的迭代器和函数对象。 对他们而言,pass-by-value往往比较合适
- 21: Don’t try to return a reference when you must return an object;
- 绝对不要返回pointer或reference指向一个local stack对象(会产生未定义的行为)
- 绝对不要返回指向一个heap-allocated对象(无法控制对象的销毁)
- 绝对不要返回pointer或reference指向一个local static对象(可能需要多个这样的对象)(在多线程情况下也难以保证安全)
- 22: Declare data members private
- 可以实现对data member的各种权限控制
- 有利于封装,注意protected 并不比public更具有封装性
- 23: Prefer non-member non-friend替换member函数
- 其实是具有更大的封装性(注意namespace)
- 增加封装性,packaging flexibility 和机能扩充性
- 24: 若所有参数需要类型转换,请将此函数设计成non-member函数
- 有理数类(Rational)的的四则运算; Rational tmp = Rational() * 2 ; Rational tmp = 2 * Rational(); 考虑这两种情况
25 : Consider support for a non-throwing swap( 不抛异常)
- 缺省版本swap
如果缺省版本的效率不够,可以尝试做
- 提供一个public swap函数(这个函数,不要抛出异常)
- 在class 或template所在的命名空间内提供一个non-member swap,并调用上述的swap成员函数
- 如果正编写一个class(非template),需要特化std::swap, 并令他调用你的成员swap
注意: 不要忘std里加入任何新的东西
// default swap
template <typename T>
void swap(T& a, T& b)
{
T temp(a);
a = b;
b = tmp;
}
class Widget {
public:
void swap(Widget &other);
}
- 26: 尽可能延后变量定义式的出现的时间(用到的时候才再去声明)
- 循环里用到的变量尽量在循环里声明,除非
- 赋值成本比构造加析构低,
- 处理代码中效率高度敏感的部分
- 循环里用到的变量尽量在循环里声明,除非
27: 尽量少做转型动作(如果转型必要,就封装在函数里面)(新式转型 好于 旧式转型)
- const_cast
- dynamic_cast
- static_cast
- reinterpret_cast 不适合移植
28: Avoid returning “handles”to object internals
- handles (pointer, reference, iterators)
29: Strive for exception safety code(为异常安全性努力是值得的)
- 带有异常安全性的函数会:
- 不泄露任何资源
- 不允许数据败坏
异常安全函数(Exception-safe code)提供下面三个保证之一
基本承诺
- 程序内的任何事物都保持在有效状态下(对象或者数据结构不会有因此而损坏)
- 当异常时,可以选用默认状态,也可以回复到变换之前的状态
强烈保证(往往以copy-and-swap实现出来)
- 函数成功,就完全成功,如果失败,程序会回复到调用函数之前的状态
- nothrow 保证
- 带有异常安全性的函数会:
30: Understand the ins and outs of inlining
- virtual 和inline同时出现,编译器往往会拒绝inline
- inline限制在小型、频繁调用的函数身上
- 构造函数最好不要inline
31: 将文件间的编译依存关系降至最低
- 接口与实现的分离
- 如果使用object reference 或者 object pointers可以完成任务,就不要使用objects
- 如果能够,尽量以class声明式替换定义式
- 为声明式和定义式提供不同的头文件
- 接口与实现的分离
32: 确定public继承是is-a的关系(注意区分has-a)
- 适用于derived class身上的所有事情都能适用于base class身上
33: Avoid hiding inherited names(避免隐藏继承的成员名)
- 避免隐藏基类的成员名 可以使用using 声明式或者 forwarding function
34: Differentiate between inheritance of interface and inheritance of implementation(区分接口继承和实现继承)
- pure virtual: 为了让derived class继承函数接口(继承了的具象类都要重新声明)
- impure virtual: 继承该函数的接口和缺省实现
- non-virtual:为了令derived classes继承函数的接口及一份强制性实现
35: 考虑virtual函数以外的其他设计方式(提及设计模式中的Template Method 和 Strategy)
- 令客户通过public non-virtual成员函数间接调用private函数(NVI手法)
/// Template Method
class GameCharacter {
public:
int healthValue() const
{
··· // 做一些事前工作
int retVal = doHealthValue();
··· //做一些事后工作
return value;
}
···
private:
virtual int doHealthValue() const
{
··· // 缺省算法,计算健康指数
}
}
-
- 借由function pointers实现strategy模式
class GameCharacter;
int defaultHealthCalc(const GameCharacter& gc);
class GameCharacter {
public:
typedef std::function<int (const GameCharacter&)> HealthCalcFunc;
explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc)
: healthFunc(hcf) {}
int healthValue() const
{ return healthFunc(*this);}
private:
HealthCalcFunc healthFunc;
};
/// 可以转换成function对象
short calcHealth(const GameCharacter&); // 返回类型 non-int
struct HealthCalculator { //为计算健康值设计的函数对象
int operator()(const GameCharacter&) const
{···}
};
class GameLevel {
public:
float health(const GameCharacter&) const; // 成员函数
···
};
-
- 使用non-virtual interface手法,那是Template Method设计模式的一种特殊形式,它以public non-virtual成员函数包裹性较低访问性(private或protected)的virtual函数
- 将virtual函数替换为“函数指针成员变量”,这是strategy设计模式的一种分解表现形式
- 以function成员变量替换virtual函数, 因此允许使用任何可以转变成(callable entity)function对象的表达式,也是strategy的一种表现形式
36: Never redefine an inherited non-virtual function.
- 如果重新定义从基类继承的non-virtual function, 那么就应该是设计出现了问题
37: 绝不重新定义继承而来的缺省参数值(针对virtual函数)
- virtual函数系动态绑定,而缺省参数值却是静态绑定
- 如果需要缺省值,应该将该函数设计为non-virtual函数,再设计一个private的virtual function 如同前面介绍的Template Method设计
38: Model “has-a”or “is-implementated-in-terms-of” through composition
- 在应用域内,是has-a的关系
- 在实现域中,是根据某物实现出 例如STL库中的stack是以vector为底层数据结构实现的
39: 明智而谨慎地使用private继承
- private继承某种意义上说 is-implemented-in-terms-of, 所以尽可能使用38条中使用复合的方式实现
- 除非确定private继承可以造成empty base最优化
40: 明智而谨慎地使用多重继承
- 多重继承比单一继承复杂;以及可能会对virtual继承的需要
- virtual继承会增加大小,速度,初始化复杂度等成本
模板与泛型编程
41: 了解隐式接口和编译器多态
- 以不同的template参数具现化function templates会导致调用不同的函数,这就是编译期多态(有点像重载函数)
- classes 和 templates都支持接口(interface)和多态(polymorphism)
- 对classes而言接口是显式的,以函数签名为中心。多态则是通过virtual函数发生于运行期
- 对template参数而言,接口是隐式(implicit)的,奠基于有效表达式;多态则是通过template具现化发生于编译期
42: 了解typename的双重意义
- 声明template参数时,class和typename可以互换
- 记得使用typename标识嵌套从属类型名称;但不要在base class List或 member initialization list内以它作为base class的修饰符
43: 学习处理模板化基类内的名称
- 继承模板基类的时候,基类的成员函数不可见;这个时候可以用一下三种方式解决
- 在前面添加this指针
- 利用using语句,使模板基类的函数可见
- 利用基类的类名加::调用基类函数
- 继承模板基类的时候,基类的成员函数不可见;这个时候可以用一下三种方式解决
44: 将与参数无关的代码抽离template
- template生成多个classes和多个function,所以任何template代码都不该与某个造成膨胀(怎么造成代码膨胀呢?)的template参数产生相依关系
- 因非类型模板参数而造成的代码膨胀,往往可以消除,做法是以函数参数或class成员变量替换template参数
- 因类型参数(type parameters)而造成的代码膨胀,往往可以降低,做法是让带有王权相同的二进制表述的instantiation types共享实现码
45: 运用成员函数模板接受所有的兼容类型
- 请使用member function templates生成“可接受所有兼容类型”的函数
- 如果你声明member templates 用于“泛化copy构造”或泛化assignment操作,还是需要声明正常的拷贝构造函数和copy assignment操作符
- 46: 需要类型转换时请为模板定义非成员函数
- 编写class template,需要支持与此template相关函数 支持参数之隐式类型转换 时,请将那些函数定义为 class template内部的friend函数
47: 请使用traits classes表现类型信息
- Trait classes使得“类型相关信息”在编译器可以用,以template特化实现
- 整合overloading技术
48: 认识template元编程
49: 了解new-handler的行为
new-handler函数要完成以下的事情:
- 让更多内存可被使用
- 安装另一个new-handler(令new-handler修改会影响new-handler行为的static数据,global数据)
- 卸除new-handler(将null指针传递给set_new_handler)
- 跑出bad_alloc(或派生出来的)异常
Nothrow new是一个颇为局限的工具,因为它只适用于内存分配 够来的构造函数还是可能跑出异常
50: 了解new和delete的合理替换时机
51: 编写new和delete时需要固守常规
52: 写了placement new也要写placement delete(不熟悉)
53: pay attention to compiler warnings
- 编译器的警告能力是不一样的; 编写程序,争取完全无警告
54: 让自己熟悉TR1在内的标准程序库(现在C++11基本都实现了)
55: 让自己熟悉boost程序库(泛型编程, 模板元编程)
没有理解和不太懂的地方,也有几处,主要集中于:
- 泛型编程
- 模板元编程
- trait type、trait class, 特化以及偏特化
接下来的学习之路也需要偏重以下上述方面。