《C++编程规范——101条规则、准则与最佳实践》(C++ Coding Standards——101 Rules, Guidelines and Best Practices)
类的设计与继承
第32条(C):弄清所要编写的是哪种类
第33条(C):用小类代替巨类
分而治之。用类表示概念。
第34条(B):用组合代替继承
即优先使用委托而非继承。
第35条(B):避免从并非要设计成基类的类中继承
本意是要独立使用的类所遵守的设计蓝图和基类大不相同,将独立类用作基类是一种严重的设计错误。
第36条(C):优先提供抽象接口
偏爱抽象艺术吧。抽象接口是完全由(纯)虚函数构成的抽象类,没有状态(即没有成员数据),通常也没有成员函数实现。注意:在抽象接口中避免使用状态能够简化整个层次结构的设计。
依赖倒置原理(DIP):1)高层模块不应该依赖低层模块。相反,两者都应该依赖于抽象。2)抽象不应该依赖细节(实现)。相反,细节应该依赖抽象。通常说的“要面向接口编程,而不要面向实现编程”也是这个意思。
第37条(C):公有继承即可替换性。继承,不是为了重用,而是为了被重用
继承塑模的是“是一个(is a kind of )”关系【Liskov替换原则】。组合塑模的是“有一个(has a kind of )”关系。
第38条(D):实施安全的覆盖
第39条(D):考虑将虚拟函数声明为非公有的,将公有函数声明为非虚拟的
在基类中进行修改代价是高昂的(尤其对于框架或库)。非虚拟接口模式(NonVirtual Interface, NVI)。
第40条(A):要避免提供隐式转换
explicit构造函数和命名的转换函数。
第41条(A):将数据成员设为私有的,无行为的聚集除外(即C语言形式的struct)
保护数据具有公有数据的所有缺点。可以考虑使用Pimpl惯用法用来隐藏类的私有数据成员。
第42条(C):不要公开内部数据
隐藏数据却又暴露句柄是一种自欺欺人的做法,就像锁上了自家的门,却把钥匙留在了锁上。
第43条(D):明智的使用Pimpl惯用法
// 将类的私有数据隐藏在一个不透明的指针后面
class Map
{
public:
// .... 接口
private:
struct PrivateImpl; // 类Map的嵌套类型
shared_ptr<PrivateImpl> m_Impl;
};
第44条(D):优先编写非成员非友元函数
要避免较成员费:尽可能优先指定为非成员非友元函数。1)减少依赖;2)分离巨类;3)提供通用性。
第45条(D):总是一起提供new和delete
第46条(D):如果提供类专门的new,应该提供所有的标准形式(普通,就地和不抛出)
构造、析构与复制
第47条(A):以同样的顺序定义和初始化成员变量
如果违反了该条规则,也会违反第1条 在高警告级别下干净利落地进行编译。
第48条(A):在构造函数中用初始化代替赋值
这可不是不成熟的优化,这是在避免不成熟的劣化。
第49条(B):避免在构造函数和析构函数中调用虚拟函数
这一点其实很好理解:因为在构造期间,对象还是不完整的,如果在基类的构造函数中调用了虚拟函数,那么调用的将是基类的虚拟函数(不管派生类是否对该虚拟函数进行了改写)。C++标准为什么这样?试想:如果调用的是派生类改写后的虚拟函数版本,那么会发生什么事情?派生类改写该虚拟函数势必会调用派生类的成员数据吧?而在构造基类期间,派生类的数据成员还没有被初始化,使用了未初始化的数据,正是通往未定义行为的快速列车。
// 使用工厂函数插入“后构造函数”调用
class B
{
protect:
B() {/*.... */ }
virtual void PostInitialize() {/*....*/}
public:
template<typename T>
static shared_ptr<T> create() // 函数模板
{
shared_ptr<T> p(new T);
p->PostInitialize();
return p;
}
};
class D : public B
{
// ....
};
shared_ptr<D> pD = D::create<D>(); // 创建一个D的对象
第50条(A):将基类析构函数设为公用且虚拟的,或者保护且非虚拟的
第51条(D):析构函数、释放和交换绝对不能失败
第52条(D):一致地进行复制和销毁
通常,拷贝构造函数,复制赋值操作符函数,析构函数要么都定义,要么都不定义。
第53条(D):显示地启用或者禁止复制
第54条(D):避免切片。在基类中考虑用克隆代替复制
将基类的拷贝构造函数声明为受保护的protected, 这样就不能将派生类对象直接传递给接收基类对象的函数,从而防止了对象切片。取而代之在基类中增加一个克隆函数clone()的定义,并采用NVI模式实现。在公有的非虚拟接口clone()函数中采用断言检查继承自基类的所有派生类是否忘记了重写virtual B *doClone()。
第55条(D):使用赋值的标准形式
第56条(D):只要可行,就提供不会失败的swap()(而且要正确的提供)
模板与泛型
第64条(C):理智的结合静态多态性和动态多态性
动态多态性是以某些类的继承体系出现的,通过虚拟函数和(指向继承层次中的对象的)指针或引用来实现的。静态多态性则是通过类模板和函数模板实现。
第65条(D):有意的进行显示自定义
第66条(D):不要特化函数模板
第67条(D):不要无意地编写不通用的代码
STL:容器
第76条(A):默认时使用vetor。否则,选择其他合适的容器
第77条(B):从vector和string代替数组
第78条(A):使用vector和string::c_str与非C++的API交换数据
vector的存储区总是连续的;大多数标准库对string的实现,也使用连续内存区(但是不能得到保证)。string::c_str总是返回一个空字符'\0'结束的C风格字符串。string::data也是返回指向连续内存的指针,但不保证以空字符'\0'结束。
第79条(D):在容器中只存储值和智能指针
第80条(B):用push_pack代替其他扩展序列的方式
第81条(D):多用范围操作,少用单元素操作
第82条(D):使用公认的惯用法真正的压缩容量,真正的删除元素
container<T>(c).swap(c); // 去除多余容量的
shrink-to-fit惯用法container<T>().swap(c); // 清空容器c
c.erase(remove(c.begin(), c.end(), value), c.end()); // 删除容器c中所有等于value的元素, erase-remove惯用法
STL:算法
算法即循环——只是更好。算法是循环的模式。开始使用算法,也就意味着开始使用函数对象和谓词。
第83条(D):使用带检查的STL实现
什么是带检查的STL实现?
第84条(C):用算法调用代替手工编写的循环
有意识的熟悉,使用STL算法吧。
第85条(C):使用正确的STL查找算法
find/find_if, count/count_if, binary_search, lower_bound, upper_bound, equal_range
第86条(C):使用正确的STL排序算法
partition, stable_partition, nth_element, partial_sort, partial_sort_copy, sort, stable_sort
第87条(C):使谓词成为纯函数
第88条(C):算法和比较器的参数应多用函数对象少用函数
第89条(D):正确编写函数对象
模板与泛型编程,C++标准模板库STL一直是自己很薄弱的地方,因为在工作中很少使用。这一来是因为自己起初就对这一块不熟悉,进而导致编程时很少使用(都不知道用有哪些功能啊),而越是这样,使用得越少,就更没有机会是熟悉STL,正是形成一个循环。STL有很多的实用功能,以后要有意识的加以使用,学习,争取掌握它。