目录
- 条款1:仔细区别pointers和references
- 条款2:尽量使用C++风格的类型转换
- 条款3:绝对不要以多态方式处理数组
- 条款4:非必要不提供默认构造函数
条款1:仔细区别pointers和references
- 在任何情况下都不能使用指向空值的引用。一个引用必须总是指向某个对象,必须有初值。
- 如果变量指向可修改,且有可能指向null,就把变量设为指针;如果变量总是必须代表一个对象(不可能为null),就把变量设为引用。
- 引用可能比指针更高效,因为不必像指针那样在使用前判断它是否有效(no null)
void printDouble(const double& rd){cout << rd; } void printDouble(const double *pd){if (pd) { cout << *pd; }}
条款2:尽量使用C++风格的类型转换
- 旧式C的转型操作符缺点:(1)不同转型意图不明确;(2)难以辨识,与C++中其他用到“(类型)”地方难以区分。
- C++通过引进四个新的类型转换操作符克服了C风格类型转换的缺点,这四个操作符是static_cast, const_cast, dynamic_cast, 和reinterpret_cast。
- static_cast:在功能上基本上与C风格的类型转换一样强大,含义也一样;不能从表达式中去除const属性。
int firstNumber, secondNumber;...double result = ((double)firstNumber)/secondNumber;double result = static_cast<double>(firstNumber)/secondNumber;
- const_cast:用于类型转换掉表达式的const或volatileness属性。
class Widget { ... };class SpecialWidget: public Widget { ... };void update(SpecialWidget *psw);SpecialWidget sw;const SpecialWidget& csw = sw; update(&csw); update(const_cast<SpecialWidget*>(&csw));
- dynamic_cast:用于安全地沿着类的继承关系向下进行类型转换。这就是说,你能用dynamic_cast把指向基类的指针或引用转换成指向其派生类或其兄弟类的指针或引用,而且你能知道转换是否成功。它不能被用于缺乏虚函数的类型上。
Widget *pw;...update(dynamic_cast<SpecialWidget*>(pw));void updateViaRef(SpecialWidget& rsw);updateViaRef(dynamic_cast<SpecialWidget&>(*pw));
- reinterpret_cast:最普通的用途就是在函数指针类型之间进行转换;转换函数指针的代码是不可移植的。
typedef void (*FuncPtr)();FuncPtr funcPtrArray[10]; int doSomething();funcPtrArray[0] = &doSomething; reinterpret_cast可以让你迫使编译器以你的方法去看待它们:funcPtrArray[0] = reinterpret_cast<FuncPtr>(&doSomething);
条款3:绝对不要以多态方式处理数组
- 数组中难免会遇到数组索引运算符array[ i ],本质上式一个指针算术表达式*(array+i),通过数组原类型的大小计算每个数组元素的地址。如果将派生类对象数组实参传递给原本为基类对象数组的形参(类似多态的方式),那么代码将仍以基类对象占用的内存大小计算对象数组的每个元素地址,问题的关键在于派生类对象几乎总是比基类对象要大,这样计算出来的元素地址就会有问题,行为是未定义的。
class BST { ... }; class BalancedBST: public BST { ... };void printBSTArray(ostream& s, const BST array[], int numElements){for (int i = 0; i < numElements; ) {s << array[i];} }BalancedBST bBSTArray[10]; ... printBSTArray(cout, bBSTArray, 10);
- 用delete [ ] 删除数组时,数组元素中每一个析构函数会被调用,依然会涉及到“指针算术表达式”,所以多态与数组不能混用:
delete [] array;for ( int i = 数组元素的个数 1; i >= 0;--i) {array[i].BST::~BST(); }
条款4:非必要不提供默认构造函数
- 需要外部数据信息来建立对象的类则不必拥有缺省构造函数。但是,如果类不提供默认构造函数,会在两个情况下遇到困难:(1)建立类对象数组时,大多数情况下需要默认构造函数进行初始化;(2)将不适用于它们无法在许多基于模板(template-based)的容器类里使用。
- 解决问题(1)的方法1:使用非堆数组(non-heap arrays)
int ID1, ID2, ID3, ..., ID10; ... EquipmentPiece bestPieces[] = {EquipmentPiece(ID1), EquipmentPiece(ID2),EquipmentPiece(ID3),...,EquipmentPiece(ID10)};
- 解决问题(1)的方法2:利用指针数组来代替一个对象数组。缺点在于必须记得对指针数组所指的所有对象进行删除,此外,内存总量会变大(指针+对象)
typedef EquipmentPiece* PEP; PEP bestPieces[10];PEP *bestPieces = new PEP[10]; for (int i = 0; i < 10; ++i)bestPieces[i] = new EquipmentPiece( ID Number );
- 解决问题(1)的方法3:使用placement new方法在内存中构造对象,指定一块大的连续内存中,在指定地址构建对象。避免了方法2中的过度使用内存的问题。缺点在于大部分程序员不熟悉placement new,维护比较困难;当你不想让它继续存在使用时,必须手动调用数组对象的析构函数,然后调用操作符delete[]来释放raw memory(已经有placement delete/delete []操作符了,它会自动调用析构函数)。
void *rawMemory =operator new[](10*sizeof(EquipmentPiece));EquipmentPiece *bestPieces =static_cast<EquipmentPiece*>(rawMemory);for (int i = 0; i < 10; ++i)new (&bestPieces[i]) EquipmentPiece( ID Number );
- List itemfor (int i = 9; i >= 0; --i)bestPieces[i].~EquipmentPiece(); operator delete[](rawMemory);delete [] bestPieces;
- 针对于问题2:没有默认构造函数的类将不适用于它们无法在许多基于模板(template-based)的容器类里使用。因为实例化一个模板时,模板的类型参数应该提供一个缺省构造函数,这是一个常见的要求。
template<class T>class Array {public:Array(int size);... private:T *data;}; template<class T>Array<T>::Array(int size){data = new T[size];...}
- 虚基类应该有一个默认构造函数,因为如果随着继承体系增加,每个派生类都需要记得基类的有参构造并了解其含义是一件痛苦的事情。
- 添加无意义的默认构造函数有时会影响类class的效率。因为成员函数会需要测试有参和无参的情况下,某些字段是否已经被初始化。