读《C++编程规范》

组织和策略问题
#0 了解哪些东西不应该标准化
注释不是越多越好,应该注意实用原则,主要用来解释方法和原理

#1 在高警告级别下编译,理解并排除所有的警告
如果引用的第三方库有警告,可以用自定义的头文件包含这个库的头文件,在自定义头文件中忽略这些警告。
如:
#pragma warning(push)//保存当前的警告
#pragma warning(disable:4512)
#pragma warning(disable:4180)
#include <boost/lambda/lambda.hpp>//第三方头文件
#pragma warning(pop)//恢复到之前的警告


#2 使用自动构建系统
使用单操作的自动化构建

#3 使用版本控制系统
CVS。不要让文件长时间登出,确保登入的代码不会影响构建成功

#4 在代码审查上投入
更多的关注有助于提高质量。亮出自己的代码,阅读别人的代码。

设计风格
不成熟的优化是程序涉及中的万恶之源
#5 一个实体应该只有一个紧凑的职责
一次只解决一个问题

#6 正确、简单和清晰第一
简单就是美(Keep It Simple Software,KISS)。
使用最简单的有效技术。

#7 编程中应该知道何时和如何考虑可伸缩性
集中精力改善算法的O(N)复杂性,而不是进行小型的优化。
要时刻了解算法的实际复杂性

#8 不要进行不成熟的优化
决不要过早进行优化,再三测试,然后在必要的时候进行优化
让一个正确的程序更快,比让一个快速的程序正确,要容易的多。
优化之前应进行度量,度量之前必须确定优化的目标。

#9不要进行不成熟的劣化
放松自己,轻松编程
引用传递代替值传递
使用前缀++代替后缀++.
使用初始化列表代替构造函数中赋值
使用抽象和库

#10 尽量减少全局和共享数据
共享容易导致冲突,增加耦合度
用通信方式代替数据共享
尽量降低类间的耦合,减少交互

#11 隐藏信息
不要从任何提供抽象的实体中公开数据

#12 懂得何时和如何进行并发性编程 (* 我没多少经验,难以理解)
避免死锁,活锁和恶性竞争条件

#13 确保资源为对象所拥有,使用显式的RAII和智能指针
资源获取即初始化(RAII)
分配资源后立即赋给属主对象。
永远不要在一条语句中分配一个以上的资源
当需要配对的获取/释放函数调用时,都应该将资源封装在一个对象中。注意复制构造和赋值操作,以及自身赋值

 

编程风格
#14 宁要编译时和连接时错误,也不要运行时错误
能在编译时做的事就不要推迟到运行时。

#15 积极使用const
当知道某个值不会改变时就把它设为const。把const变成定义值时的默认选项
使用mutable

#16 避免使用宏
只在几个无法避免的地方使用:
#include 保护符
#ifdef和#if defined
assert实现

#17 避免使用魔数
使用符号名称代替它,符号名称易于理解和维护
使用const,枚举类型活函数

#18 尽可能局部地声明变量
变量的作用域越小越好,生存期越短越好

#19 总是初始化变量
初始化数组和结构的简单方法:

ing total[100] = {0};
struct Data
{
int i;
double d;
char szName[10];
};
Data d={0};

注意,volatile型数据不需要程序对其进行初始化

#20 避免函数过长,避免嵌套过深

#21 避免跨编译单元的初始化依赖
保持初始化顺序
尽量避免名称空间级的变量

#22 尽量减少定义性依赖。避免循环依赖
如果可以用前向声明就不要#include
只有两种情况需要某个类的完整定义:
1 需要知道该类的大小
2 需要命名或者调用该类的成员
打破循环的一个方法就是,不要让高层模块依赖低层模块,而是让两者都依赖于抽象

#23 头文件应该自给自足 (* 一值对什么时候应该包含头文件很迷惑,好好理解一下#22和#23)
应该确保所编写的每个头文件都能够独自编译,即当需要用到某个头文件中的内容时,只需要包含此文件,而不需要包含其他的文件。
模板是在其定义处编译的,但非独立名称或者类型要等到模板实例化时才编译。

#24总是编写内部#include保护符,决不要编写外部#include保护符

//for foo.h
#ifndef FOO_H_INCLUDED_
#define FOO_H_INCLUDED_
//......
#endif

函数与操作符
#25 正确地选择通过值,(智能)指针或引用传递参数

#26 保持重载操作符的自然语义

#27 优先使用算术操作符和赋值操作符的标准形式
定义了a+b 也应该定义a+=b.并且用用+=来定义+.

#28 优先使用++和--的标准形式。优先调用前缀形式
应该用前缀形式实现后缀形式
T& T::operator++() //前缀形式
{
//执行递增
return *this;
}

T T::operator++(int) //后缀形式
{
T old(*this); //保存旧值
++*this;
return old;
}

#29 考虑重载以避免隐含类型转换
奥卡姆剃刀原理:如无必要勿增对象。重载会引起临时对象的创建。

#30 避免重载&& ||和,
因为内置版本的特殊之处在于:从左到右求值,(对&& ||)短路求值
而函数参数总是会对所有参数进行求值,且求值顺序是不确定的。

#31 不要编写依赖于函数参数求值顺序的代码
函数参数的求值顺序是不确定的

类的设计与继承
软件开发最重要的一个方面就是弄清楚自己要构建的是什么。
#32 弄清所要编写的是哪种类
值类 模仿内置类型
基类 构成类层次结构
traits类
策略类
异常类
附属类

#33 用小类代替巨类
分而治之:小类易于编写、测试和使用

#34 用组合代替继承
继承是仅次于友元的第二紧密的耦合关系
软件工程中的一条明智原则就是,尽量减少耦合
通过(智能)指针保存对象,可以减少头文件依赖性
Pimpl技术:将所有私有成员聚合在一个不透明的指针后面
必须使用继承的情况:
1 需要改写虚拟函数
2 需要访问保护成员
3 需要在基类前构造已使用过的对象,或者在基类之后销毁此对象(基类比子类先构造,后析构)
4 需要操心虚拟基类

#35 避免从非必要设计成基类的类中继承
独立类和基类的设计有不同的考虑

#36 优先提供抽象接口
优先采用实现了抽象接口的设计层次结构

#37 公用继承即可替换性。继承,不是为了重用,而是为了被重用
公用继承描述的是“is-a”或者“works like a”关系
组合描述的是“用...来实现”关系

#38 实施安全的改写

#39 考虑将虚拟函数声明为非公用的,将公用函数声明为非虚拟的

#40 避免提供隐式转换
隐式转换构造函数:能够用一个参数调用且未声明为explicit的构造函数。

#41 将数据成员设为私有的、无行为的聚集

#42 不要公开内部数据

#43 明智地使用Pimpl
实现编译器防火墙
class A
{
private:
struct Impl;//存储所有私有成员
shared_ptr<Impl> pimpl_;
}

#44 优先编写非成员非友元函数

#45 总是一起提供new和delete

#46 如果提供类专门的new,应该提供所有标准形式(普通、就地和不抛出)
void* operator new(std::size_t);//普通
void* operator new(std::size_t,std::nothrow_t) throw();//不抛出
void* operator new(std::size_t,void*);
定义了一个某种形式的重载会隐藏其他的重载形式

构造、析构与复制
#47 以同样的顺序定义和初始化成员变量
成员变量初始化顺序与在类中声明的顺序一致

#48 在构造函数中用初始化代替赋值

#49 避免在构造函数和析构函数中调用虚拟函数
虚拟函数在构造函数和析构函数中并不虚拟

#50 将基类析构函数设为公用且虚拟的,或者保护且非虚拟的

#51 析构函数、释放和交换绝对不能失败

#52 一致地进行复制和销毁
同时定义复制构造函数,复制赋值操作符和析构函数

#53 显式地启用或禁止复制

#54 避免切片,在基类中考虑用克隆代替复制

#55 使用赋值的标准形式

#56 只要可行,就提供不会失败的swap

名字空间与模块
模块:同一个人或小组维护的紧凑的发布单元
#57 将类型及其非成员函数接口置于同一名字空间中
对于一个类X而言,所有在同一个名字空间中提及X和随X一起提供的函数逻辑上都是X的一部分,它们形成了X接口的组成部分。
ADL 参数依赖查找

#58 应该将类型和函数分别置于不同的名字空间中,除非有意想让它们一起工作

#59 不要在头文件中或者#include之前编写名字空间using
在头文件中不要使用using指令或声明,而应该显示使用名字空间限定所有名字
不要在#include之前使用using

#60 避免在不同的模块中分配和释放内存

#61 不要在头文件中定义具有链接的实体

#62 不要允许异常跨越模块边界传播

#63 在模块的接口中使用具有良好可移至性的类型
不要让类型出现在波快的外部接口中,除非能确保所有的客户代码都能够正确地理解该类型。
使用客户代码能够理解的最高层抽象。
抽象层越低,可移至性越好。

模板与泛型
#64 理智地结合静态多态性和动态多态性
静态多态性是指模板类和模块函数
动态多态性是指类的虚拟函数和间接引用

#65 有意地进行显示自定义

#66 不要特化函数模板

#67 不要无意地编写不通用的代码

错误处理与异常
问题不在与我们是否会犯错误,而在于我们是否会安排编译器和工具寻找错误。

#68 广泛地使用断言记录内部假设和不变式
一个事件中所含的信息量与该事件发生的概率是成反比的。
不要使用断言报告运行时错误

#69 建立合理的错误处理策略,并严格遵守
只在模块边界处改变错误处理机制

#70 区别错误与非错误
前条件
后条件
不变式

#71 设计和编写错误安全代码
基本保证:确保出现错误时程序会处于有效状态。
强保证:保证最终状态要么时最初状态,要么是所希望的目标状态。
不会失败保证:保证操作永远不会失败。

#72 优先使用异常报告错误
使用异常而不是错误码

#73 通过值抛出,通过引用捕获

#74 正确地报告、处理和转换错误

#75 避免使用异常规范

STL容器
#76 默认使用vector,否则,选择其他合适的容器

#77 用vector和string代替数组

#78 使用vector与非C++API交换数据
&*iter:获取迭代器引用元素的地址

#79 在容器中只存储值和智能指针
在容器中存储值对象

#80 用push_back代替其他扩展序列的方式
push_back 是按指数级扩大容量的

#81 多用范围操作,少用单元素操作

#82 使用公认的惯用法真正地压缩容量,真正地删除元素
swap:
container<T>(c).swap(c);//去除多余容量的shrink-to-fit惯用法
container<T>().swap(c);//去除全部内容和容量的惯用法
c.erase(remove(c.begin(),c.end(),value),c.end());//删除元素的惯用法

STL算法
多用算法,少用循环
#83 使用带检查的STL实现

#84 用算法调用代替手工编写的循环
采取“处理此范围”的算法性思维方式,取代“处理每个元素”的循环式思路
使用boost.lambda库

#85 使用正确的STL查找算法
查找无序范围:find/find_if
查找有序范围:low_bound, upper_bound, equal_range, binary_search.
对有序范围,p=equal_range(first,last,value);distance(p.first,p.end);比count(first,last,value);更快

#86 使用正确的STL排序算法
开销从低到高:partition,stable_partition,nth_element, partial_sort(partial_sort_copy),sort,stable_sort

#87 使谓词成为纯函数
将谓词类型的operator()声明为const

#88 算法和比较器的参数应多用函数对象少用函数

#89 正确编写函数对象
将函数对象设计为复制成本很低的值类型。尽可能从unary_function或binary_function继承

类型安全
#90 避免使用类型分支,多使用多态

#91 依赖类型,而非其表示方式

#92 避免使用reinterpret_cast

#93 避免对指针使用static_cast
使用dynamic_cast

#94 避免强制转换const

#95 不要使用C风格的强制转换

#96 不要对非POD进行memcpy或者memcmp操作

#97 不要使用联合重现解释表达方式

#98 不要使用可变长参数
boost.format库

#99 不要使用失效对象。不要使用不安全函数
不要使用不安全的C语言遗留函数

#100 不要多态地处理数组
基类对象的数组和子类对象的数组是完全不同的类型

posted on 2006-05-15 09:52 随便写写 阅读(529) 评论(0)  编辑 收藏 引用 所属分类: 读书笔记


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


导航

<2024年3月>
252627282912
3456789
10111213141516
17181920212223
24252627282930
31123456

统计

常用链接

留言簿(1)

随笔分类(30)

随笔档案(16)

文章分类(18)

文章档案(9)

链接

搜索

最新评论

阅读排行榜

评论排行榜