条款02 尽量以const,enum,inline替换#define
-
define nTemC 5
并没有使得nTemC被编译器看见,有可能就没有进入记号表内。于是当使用此常量导致的编译错误可能会提及5而不提及nTemC,这将使问题难以追踪。并且由于预处理器盲目地将宏名称替换为其对应的东西而导致代码产生冗余。const int nTemC = 5;
则不会出现上述的问题。当在类中使用类的专属常量的时候可以使用const来实现,不能用#define实现,因为#define不注重作用域,一旦宏被定义,它就在其后的编译过程中有效(除非在某处被#undef) - 若要在头文件内定义一个常量字符串,必须写const两次
const char* const pStrC = "1" \\第一个const是用于修饰指针所指字符串,加上这个const是很好的习惯,第二个const是修饰指针本身,定义于头文件的变量必须是常量,所以第二个const是必须的
- 定义于类内static const变量最好在类外提供定义
class CTest
{
public:
const static int CSValue0 = 1; //类的专属常量的声明,附带初始值。
const static int CSValue1; //类的专属常量的声明,无初始值
};
const static int CSValue0; //类的专属常量的定义。
const int CTest::CSValue1 = 1; //类的专属常量的定义。
-
在class编译期间需要一个class常量值也可以使用类内枚举。对一个const变量取地址是合法的,对一个枚举值取地址是非法的,而取一个#define的地址通常也是不合法的
-
优秀的编译器不会为整形const对象设定另外的存储空间(除非创建一个指针或引用指向该对象),不过不够优秀的编译器(包括vs2010)却可能为const对象创建存储空间。但是enum和#define都绝不会导致非必要的内存分配
-
宏看起来像函数,但是不会招致函数调用带来的额外开销,使用inline则没有相应的问题
-
有了const enum inline 我们对预处理器(特别是#define)的需求降低了,但是并非完全消除。#include仍然是必需品。#ifdef/#ifndef也扮演控制编译的重要角色。
条款03 尽可能的使用const
- 令函数返回一个常量值,往往可以降低因客户错误而造成的意外,而不至于放弃安全性和高效性。
const operator *(const CTest &Test1, const CTest &Test2); //可以有效防止比如: (Test0 * Test1) = Test2;
- 将不需要被改变的量声明为const的,有助于在判断表达式中意外将==写成=造成的错误,以及防止其被意外修改。
- 将const作用于成员函数的目的是为了确认该成员函数可作用于const对象上。
- 区分顶层const和底层const,顶层const表示const所修饰的变量本身是常量,而底层const一般作用于指针,表示指针所指对象是常量。
- 将成员变量声明为mutable的,则即使在常量成员函数中也能修改这个变量的值。
- 当成员函数的常量版本和其非常量版本有着实质的等价实现的时候,最好是在其非常量版本中调用常量的版本,反之则违背了常量成员函数不修改其成员的规则,可能会导致逻辑错误。
- 不论将const用于何处,都能很好的向使用者说明当前const所修饰的符号将不会被更改。在开发或者调试的时候,是很节约review时间的
条款04 确定对象被使用前已经被初始化
- 读取未初始化的值会导致不明确的行为。
- C++对于定义在不同cpp中的全局变量的初始化顺序没有进行规定,所以一个cpp中的全局变量不要依靠另一个cpp中的全局变量进行初始化,否则可能会产生bug
- 将全局变量局部static化。将每个全局变量搬到自己的专属函数中,然后由用户调用这些函数以获取其需要的变量。C++保证,函数内的局部static变量会在该函数调用期间,首次遇上该对象的定义式时被初始化(当此函数没有被调用的时候就省去了此变量的构造和析构)。
int& fun()
{
static int value = 10;
return value;
}
- 构造函数最好使用成员初始化列表进行对其成员的初始化。
条款06 若不想使用编译器自动生成的构造函数、析构函数等函数,则明确的拒绝
- 将对应函数声明为private且不进行定义,防止外部函数、友元函数、成员函数进行调用
条款07 为多态基类声明virtual析构函数
- 基类的析构函数不定义为虚函数,则不要对其进行派生,否则由于基类的指针或引用可以指向派生类的对象,则在删除基类对象的时候可能会出错,导致破坏数据结构。
- 不要为普通的类声明一个虚析构函数。这是因为C++为了实现虚函数,会建立一个由函数指针构成的数组,称为虚函数表,导致类的对象必须携带额外信息来决定运行期间哪个虚函数会被调用,这个额外信息通常由一个指针组成,此指针指向虚函数表。当为类声明有虚函数的时候,会使得类的对象占用更多的内存,在32位程序下,会多出4字节,在64位程序下会多出8字节
条款08 别让异常逃离析构函数
- 析构函数绝对不要吐出异常,如果一个被析构函数调用的函数有可能抛出异常,析构函数应该捕获任何异常,然后吞下他们或者结束程序
- 如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么类应该提供一个普通函数而非在析构函数中执行该操作
条款09 绝对不要在构造和析构过程中调用虚函数
- 如下代码,当构造CChild类的成员的时候,其父类的成员首先被构造,则先调用其父类的构造函数,其父类构造函数中调用了一个虚函数,此时并不会发生动态绑定,这里调用的虚函数是父类的版本。对象析构的时候也是如此。
class CFather
{
public:
CFather() {Fun();}
virtual void Fun(){printf("Father virtual ");}
};
class CChild : public CFather
{
public:
CChild() {Fun();}
virtual void Fun(){printf("Child virtual ");}
};
CChild child;
//输出Father virtual Child virtual 而不是 Child virtual Child virtual
条款10 令operator=返回一个reference to *this
- 是为了能进行连续复制,以符合类似于内置类型的操作
条款11 在operator=中处理自我赋值
条款12 复制对象时勿忘其每一个成分
- 复制所有本类的成员变量,调用其基类的适当的拷贝函数
- 通常拷贝构造函数与拷贝赋值运算符所做的工作很接近,但是最好不要让其中一个调用另一个,应该定义一个新的函数,供这两个函数调用。
- 令拷贝赋值运算符调用拷贝构造函数是不合理的,因为这就像试图构造一个已经存在的对象,这是很荒谬的。
- 令拷贝构造函数调用拷贝赋值运算符也是不合理的,因为拷贝构造函数是用来初始化新对象,而拷贝赋值运算符只实施在已初始化的对象上,所以从逻辑上讲也是不合理的。
网友评论