组织策略
0,不拘于小结
缩进, 行的长度,命名,注释,空格,制表,
1-4,高警告级别干净利落地进行编译,使用构建系统,使用版本控制,代码审查
风格
5,一个实体应该只有一个紧凑的职责。 (依赖性管理,继承,抽象,隐藏)
6,正确简单清晰
7,
===================
01, 视c++为一个语言联邦
multiparadigm programming langauge, 支持过程(procedrual),面向对象object oriented, 函数形式functional
,泛型形式generic, 元编程metaprogramming
02, 尽量用const 取代enumu,inline替换#define
03, 尽量用const
const成员函数目的,为了该成员函数作用于const对象身上
两个函数如果常量性不同,可以被重载
const对象大多数用于passed by poiner-to-const, passed by reference-to-const
bit constness,成员函数只有在不更改任何non-static成员对象才可以说是const,也就是说不更改对象内任何一个bit.
logical constness,一个const成员函数可以修改它所处理的对象内的某些bits,但只有客户端侦测不出的情况下才得如此。
mutable 能够释放掉non-static 成员变量的bitwise constness约束。
const char & operator[](std::size_t position) const
{
return text[position];
}
char & operator[](std::size_t position)
{
return const_cast<char&>(static_cast<const TextBlock&>)(this)[position]);
}
第一次转型static_cast<const TextBlock&>)(this)
第二次转换const_cast<char&>
利用const实现non const版本
反向做法不可以。const成员函数承诺不改变其对象的逻辑状态(logical state),non-const成员函数没有这样的承诺。
如果const调用non-const,就冒这样的风险:你曾经承诺不改动的那个对象被改动了。
编译器强制实施bitwise constness, 编程应该使用概念上的常量性(conceptual constness)
04, 确定对象使用前没初始化
变量初始化规则,取决于变量类型和位置
1)内置类型变量初始化取决于变量定义的位置,函数体外定义的变量都初始化成0,函数体里定义的内置类型不进行自动
初始化。
2)类类型变量的初始化,通过构造函数初始化(有默认/无默认需要显示调用)
non-local-static 对象初始化顺序无法确定
对应内置对象手工初始化
使用成员初始值列表,不要在构造函数里面赋值,免除“跨编译单元之初始化次序”问题,用local static 对象替换
non-local static对象
05,了解c++默认编写哪些函数
default构造函数,拷贝构造函数,拷贝赋值
06,若不使用编译器自动生成的函数,就该明确拒绝。
uncopable 将构造函数声明为private
07,为多态声明virtual 析构函数
08,别让异常逃离析构函数
09, 绝不在构造函和析构过程中调用virtual函数
可以用private static函数,作为辅助函数给base calss构造函数传值。
10, 另operator= 返回一个reference to *this
连锁赋值。这只是个协议,无强制性。内置标准类型和标准库都提供
11,在Operator= 中处理自我赋值
确定任何函数如果操作一个以上对象,其中多个对象是同一个对象时,执行仍然正确。
12, 复制对象时勿忘其每一个成分
1)复制local成员变量,2)base class内的coppying函数
copy assignment调用copy构造函数是不合理的。因为这就像试图构造一个已经存在的对象。
copy构造函数调用copy assignment操作同样无意义。 构造函数用来初始化新对象,而assginment
操作只实施于已经初始化的对象上。对一个尚未构造好的对象赋值,就像在一个尚未初始化的对象上
做“只对已经初始化对象才有意义”的事情一样。
如果你发现copy构造函数和copy assignment操作符有相近的代码,消除重复代码的做法是,
建立一个新的成员函数给两者调用。这样的函数往往是priviate,而且常命名init.
1)copy函数应该确保复制对象内的所有成员变量及base成分
2)不要尝试以某个copying函数实现另一个copying函数。应该将共同机能放在第三个函数中,
并由copying函数共同调用。
资源管理
文件描述符,互斥锁,图像界面字体笔刷,数据库,socket
13, 以对象管理资源
获得资源后立刻放进管理对象auto_ptr RAII resource acquisition is initialization
管理对象运用析构函数确保资源释放
RCSP reference-couting smart pointer RCSP无法打破环状引用。
tr1::shared_ptr就是RCSP
为了防止资源泄漏,请使用RAII对象,它们在构造函数中获得资源并在析构函数中释放资源
两个常被使用的RAII classes分别是tr1_shared_ptr和auto_ptr
14, 在资源管理类中心小心copying行为
15,在资源类中提供对资源的访问
RAII class 应该提供取得其管理之资源的办法
对原始资源的访问可能经由显示转换或隐士转换,一般而言显示转换比较安全,隐士转换对客户比较
方便
16,对使用new和delete要采用相同的形式
delete pal; delete [] pal;
17,以独立newed对象需要置入智能指针
以独立语句将Newed对象置于智能指针内,如果不这样,一旦异常抛出,有可能导致难以觉察的资源泄漏
设计与声明
18,让接口容易被正确使用,不容易被误用
理想上,如果客户企图使用某个接口却没有获得他所预期的行为,这个代码不应该通过编译,反之则是
客户想要的
接口一致性。尽量与内置types一致。
限制类型内什么事可以做,什么不可以做,常用const限制。
防止误用,包括建立新类型,限制类型上的操作,束缚对象值,消除客户端资源管理责任
tr1::shared_ptr支持定制删除器,可以防范DLL问题,可被用来自动解除互斥锁。
19 设计class犹如设计type
重载函数,操作符,控制内存分配,定义对象初始化终结。
新type的对象应该如何被创建销货
对象的初始化和对象的赋值应该有什么样的差别。这个答案决定构造函数和赋值操作符的行为
新type对象如果被passed by value,意味着什么? copy构造函数
什么是typed的合法值。 class约束条件 invariants.
你新type需要配合某个继续体系图么?
你的type需要什么样的转换?
什么样的函数对新type而言合理,memeber函数
什么样的标准函数应该驳回, private函数
谁该取用新type成员。public成员
什么是新type的未声明接口
你的新type有多么一般化 class template
你需要一个新type么?
20,宁以pass-by-reference-to-const 替换pass-by-value
防止产生副本copy构造函数多次调用
防止对象切割,子类被视为基类对象传递
这个规则不适用于内置类型
21, 必须返回对象时,别妄想返回其reference.
绝不要返回Pointer或reference指向一个local stack对象,或返回reference指向
一个heap-allocated对象,返回pointer或reference指向local static对象有可能
同时需要多个这样的对象。
22,将成员变量声明为private
23,宁以non-member ,non-friend替换member函数
数据以及操作数据的那些函数应该捆绑到一块。
如果某些东西被封装,它就不再可见。愈多东西被封装,愈少人可以看到它。而遇少
人看到它,我们就有愈大的弹性去变化它。因为我们改变仅仅直接影响看到改变的那些
人和事物。因此,愈多东西被封装,我们改变那些东西的能力就愈大。封装使我们改变
事物只影响有限客户。
限制考虑对象数据,愈少代码可以看到数据(也就是访问它),愈多的数据可以被封装
,而我们也就遇能自由地改变对象数据,例如改变成员变量的数量,类型等等。如何测量
“有多少代码可以看到一块数据”,我们计算能访问该数据的函数,作为一种粗糙的测量。
愈多函数访问它,数据的封装性愈低。
名字空间可以跨多个源文件,类不能,类是一个整体不能分割。
prefer non-member non-friend to member,这样做增加封装性,包裹弹性,和机能扩充性。
24,若所有参数皆需类型转换,请为此采用non-member函数
只有参数被列入参数列,这个参数才是隐式类型转换的合格参与者。
member的函数反面是non-member,而不是friend.
不能够只因为不该成为member,就自动让它成为friend.朋友带来的麻烦往往过于其价值。
25,考虑写出一个不抛出异常的swap函数。
无法偏特化function template.只能对class template偏特化。
可以重载function template
std::swap 典型实现
namespace std {
template<typename T>
void swap(T& a, T&b)
{
T temp(a);
a = b;
b = temp;
}
}
如果不赋值,只想拷贝指针pimp.可以对std::swap特化,下面是基本构想,但目前无法通过编译
namespace std{
template<> void swap<Widget>(Widget &a, Widget& b)
{
swap(a.pmpl, b.pmpl);
}
}
在Wiget类中加一个成员swap函数,这种做法同STL容器一致。
class Widget {
public:
void swap(Widget & other)
{
using std::swap;
swap(pImpl, other.pImpl);
}
};
如果类Widget 和 WidgetImp是模板
template<T>
class WidgetImp {...};
template<T>
class Widget{...};无法在std内添加重载模板,违反标准。
namespace WidgetStuf
{
...
template<typename T>
class Widget {...};
...
template<typename T>
void swap(widget<T> a, widget<T>b)
{
a.swap(b);
}
}
如果打算置换两个widget对象,根据c++名字查找法则 name lookups rules. argument-dependent lookup或koening lookup法则
pimp pointer to implementation.