牛骨文教育服务平台(让学习变的简单)

Effective C++ 45-48

45。弄清c++在幕后为你所写,所调用的函数。

如果设置一个空类,c++编译器会声明以下函数:拷贝构造函数,赋值运算符,析构函数,一对取地址运算符函数(const和非const)。而如果你没有声明任何构造函数的话,编译器会为你声明一个缺省构造函数。这些函数都是公有的。

编译器生成的缺省构造函数和析构函数实际上什么也不做,生成的析构函数一般是非虚构的,除非继承了一个具有虚析构函数的基类。缺省取地址符只是返回对象的地址,即return this。而拷贝构造函数和赋值运算符,对类的非静态数据成员进行“以成员为单位”逐一拷贝构造或赋值,也就是浅拷贝。

当类中有引用时,默认的拷贝函数无法实现,编译器会报错,有常量也是,有指针是,会发生浅拷贝但是运行上没有错误。对于含有指针,引用和const成员的类需要自己定义赋值运算符和复制构造函数。而如果将派生类中的赋值运算符或拷贝构造函数声明为private,编译器也会拒绝为这个派生类生成相应的赋值运算符和拷贝构造函数。

46.宁可编译和链接时出错,也不要在运行时出错。

当通过编译和链接后,只有极少数情况会让C++抛出异常,如内存耗尽,运行时错误和C++没什么关系。C++没有运行时检测,要尽量避免运行时错误。

对于运行时错误,在一个运行中没有错误,并不表示其就是正确的了,因为每次程序运行的状态都不一样。

而避免运行时错误的一般方法是对设计做一些小小的改动,就可以在编译期间消除可能产生的运行时错误。一般设计在程序中增加新的数据类型,以在编译时检测数据的安全性。

对于一个日期类,有构造函数:Date(int day,int month,int year);实现这个类面临的问题是对day和month进行合法性检测,如果不进行检测,由于其内部逻辑可能会导致一些运行时错误。一种简单的方法是使用枚举 

enum Month {Jan = 1,Feb = 2,....,Dec =12};

而构造函数改为:

Date(int day,Month month,int year);

但是这样做没有多大好处,因为枚举类型不用初始化,即直接 Date d(1,Month m,2014),能通过编译,但是运行时出错。

即想免除运行时检查,又要保证足够的安全性,选择使用一个类来实现month。

class Month{
public:
	static const Month Jan(){return 1;}//这里其实是调用隐式构造函数,其实返回值为 Month(1);
	//....
	static const Month Dec(){return 12;}//使用静态函数,返回一个常量,防止随意改动
	int toInt() const 
	{return number;}
private:
	Month (int n):number(n){}
	const int number;
};

这里调用类的静态成员返回对应的Month,而构造函数隐藏,防止用户自己去创建新的month。但即使有了这样的类,用户还是可以指定一个非法的month,如下:

	Month* m;
	Data(1, *m ,2014);

消除所有的运行时检测是不切实际的。但将检查由运行时转移到编译或链接时一直值得努力的目标,这样做,会使程序更小,更快,更可靠。

47.确保非局部静态对象在使用前被初始化。

使用对象前一定要初始化。

非局部静态对象是指 : 定义在全局或名字命名空间内,或在一个类中被声明为static,或在一个文件范围内被定义为static。就是值全部的对象,去掉非静态 的局部变量 和函数内的静态变量。

当类依赖与这些非局部静态对象时,如在 一个文件中有一个全局对象theCountry, 在另外一个文件中有一个对象theCity,对city的初始化依赖与country的初始化。而程序的正确运行依赖于它们的初始化顺序。但确定非局部静态对象初始化的正确顺序很困难,在多个编译单元中确保每个这样的对象初始化是很困难的,尤其当程序变得更加复杂增加更多的这种非局部静态对象的情况下。

单一模式,将每个非局部静态对象转移到函数中,声明其为static,其次,让函数返回这个对象的引用。这样用户就可以通过函数调用来指明对象,即用函数内部的static对象来取代非局部静态对象。因为对于函数的静态对象什么时候被初始化,c++明确的指出了。这样的另一个好处是如果这个模拟非局部静态对象从没被调用,也就永远没有对象构造和销毁的开销。简单的例子:

class country{....};
country& theCountry(){
	static country tc;//定义和初始化theCountry
	return tc;//返回它的引用。
}

48.重视编译器警告。

一般程序员都会忽略编译器警告,毕竟没有出错。要理解编译器的各种警告的含义。书上举个例子:

class A{
public:
	virtual void f() const{cout<<"fA";}
};
class B:public A{
public:
	virtual void f(){cout<<"fB";}
};

书上说有编译器在这里会出一个 B::f() hides virtual A:::f()的错误,即A中声明的f函数并没有在B中重新定义,但是被B中新声明的非const的f函数给隐藏了。但是这样没有实现多态,对于使用声明为A的指针的B的对象指向f函数的话,会调用A中的f函数。但是我用的vs2012中并没有提示这条警告。