Effective C++阅读记录(tips 1 - 4)

Tips01:View C++ as a federation of languages

把C++看作语言的联邦。C++包括了C、 Object-Oriented C++(C with class)、 Template C++、 STL。
C++高效守则视情况而变化,取决于你使用C++的哪一部分。

Tips02:尽量以const, enum, inline替换#define

#define不被视作源码的一部分,在源码被编译器处理之前就被预处理器处理了。所以当#define出现错误时,会很难去追踪这个错误。最好的处理就是使用const常量代替宏。在替换时有两点需要注意:

1、定义常量指针。

由于常量定义通常放在头文件内,所以指针也必须时const类型,因此在定义时必须const两次。eg:

const char* const authorName = "xxxx";

2、定义类的专属常量。

由于常量必须限制在类中,为了确保常量最多只有一份实体,所以它必须成为一个静态成员。eg:

class GamePlayer{
	private:
		static const int NumTurns = 5;//声明
		int scores[NumTurns];
		...
	};

如果需要使用这个类的专属常量的地址,需要在该文件中提供该常量的定义。eg:

const int GamePlayer::NumTurns;//由于在声明时已经获得了初值,所以在定义的时候可以不用给予初值。

the enum hack补偿做法:

理论基础:一个属于枚举类型的数值可权充int被使用。
class GamePlayer{
	private:
		enum(NumTurns = 5); //"the enum hack"令NumTurns成为5的一个记号名称
		int scores[NumTurns];
	} 

枚举可以阻止别人通过指针或者引用指向你的某个整数常量。即别人无法通过指针或者引用访问枚举的常量。

使用inline函数代替宏定义的函数。

	#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))

	template<typename T>
	inline void callWithMax(const T& a, const T& b)
	{
		f(a > b ? a : b);
	}

Tips03:尽可能使用const

可以用const在class外部修饰global或者namespace作用域中的常量,或修饰文件、函数、或区块作用域中被声明为static的对象。你也可以用它修饰class内部的static和non-static成员变量。而对于指针,你也可以指出指针自身、指针所指物,或者二者都是const。

const虽然变化多,但并不困难。如果const在星号左边,则表示被指物为常量,如果在星号右边,则表示指针自身是常量。如果出现在星号两边,则说明指针和被指物都是常量。

const成员函数

将const实施于成员函数的目的,是为了确认该成员函数可作用于const对象身上。
许多人漠视一件事实:两个成员函数如果只是常量性不同,可以被重载

class TextBlock{
public:
	...
	const char& operator[](std::size_t position)const
	{return test[position];}
	char &operator[](std::size_t position)const
	{return test[position];}
private:
	std::string test;
}
int main()
{
	TextBlock tb("HELLO WORLD!");
	const TextBlock ctb("hello world!");
	std::cout<<tb[0]<<std::endl;
	std::cout<<ctb[0]<<std::endl;
	return 0;
}

请记住

1、将某些东西声明为const可帮助编译器侦测出错误用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。
2、编译器强制实施“bitwise constness”,但编写程序时应该使用概念上的常量
3、当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复。

Tips04:确定对象在使用前被初始化

内置型对象

需要手动为内置型对象进行初始化,因为C++不保证初始化它们。

int t;
class Point{
	int x, y;
};
Point p;

在某些情况下x 以及p的成员会被初始化,但有时候不会。这会导致初始值不明确。
通常如果你使用C part of C++(见条款1)而且初始化可能招致运行期成本,那么就不保证发生初始化。一旦进入non-C parts of C++,规则有些变化。这就很好地解释了为什么 array(来自C part of C++)不保证其内容被初始化,而vector(来自 STL part of C++)却有此保证。
最好的处理办法就是:永远在使用对象之前将他初始化。对于无任何成员的内置类型,必须手工完成。

非内置型对象

对于内置类型意外的任何东西,初始化责任就放在了构造函数身上。规则很简单:确保每一个构造函数都将对象的每一个成员初始化。
构造函数的一个较佳的写法是,使用member initialization list(成员初始化列表)替换复制动作。

class PhoneNumber{...};
class ABEntry{
public:
	ABEntry(std::string name, std::string address)
	:theName(name), theAddress(address), numTimesConsulted(0){};
private:
	std::string theName;
	std::string theAddress;
	int numTimesConsulted;
};

如果在定义时,没有给参数,我们可以再额外重构一个无参数的构造函数。

class PhoneNumber{...};
class ABEntry{
public:
	ABEntry(std::string name, std::string address)
	:theName(name), theAddress(address), numTimesConsulted(0){};
	ABEntry():theName(), theAddress(), numTimesConsulted(0){};
private:
	std::string theName;
	std::string theAddress;
	int numTimesConsulted;
};

有些情况下即使面对的成员变量属于内置类型,也一定要使用初始化列表,比如成员变量为const 或 references,他们必须有初值,而且不能被赋值。为避免需要记住成员变量何时需要在成员初始化列中初始化,何时不需要。最简单的做法就是:使用成员初始化列表,这样做有时候绝对必要,而且比赋值更加高效。

C++ 有着固定的成员初始化次序,次序总是相同:base ckasses 更早于其derived ckass被初始化。而class的成员变量总是以声明次序被初始化。

不同编译单元内定义之non-local static对象

static对象指其寿命从被构造出来直到程序结束位置。这种对象包括global对象、定义于namespace作用域内的对象、在class内、函数内、以及file作用域内被声明的static对象。函数内的static称为local static对象,其他static对象称为non-local static对象。程序结束时static对象会自动销毁。
编译单元指产出单一目标文件的那些源码。基本上它是单一源码加上其所含入的头文件。
所以本部分主要涉及至少两个源码文件,每个内都至少含有一个non-local static对象。问题在于如果某编译单元内的某个non-local static对象的初始化作用使用了另一个编译单元内的某个non-local static对象,它所用到的这个对象可能尚未被初始化。因为C++对“定义于不同编译单元内的non-local static对象”的初始化次序并无明确定义。
解决办法:将每个non-local static对象搬到自己的专属函数内(该对象在此函数内被声明为static),这些函数返回一个reference指向它所含的对象。然后用户调用这些函数,而不直接指涉这些对象。换句话说:non-local static对象被local static对象替换了。(单例模式)

总结

1、为内置型对象进行手工初始化,因为C++不保证初始化它们。
2、构造函数最好使用成员初始化列表,而不要在构造函数内使用赋值操作,初始列列出的成员变量,其排列次序应该和它们在class中的声明次序相同。
3、为避免“跨编译单元之初始化次序”问题,请以local static对象替换non-local static对象。

上一篇:关于GIT的一些TIPS


下一篇:JavaScript支持正则表达式的String对象的方法