条款1:指针与引用的区别
- 当你知道你必须指向一个对象并且不想改变其指向时,或者在重载符并为防止不必要的语义误解时,你不应该使用指针。除此之外的情况下,则使用指针。
条款2:尽量使用C++风格的类型转换
- 将一个pointer-to-const-object转型为一个pointer-to-non-const-object(改变对象常量性,和将一个pointer-to-base-class-object转型为一个pointer-to-derived-class-object(改变对象类型)有很大差异。而传统C转型动作并无区分,不过旧式的C转型动作并非唯一选择,C式转型式为C设计的,不是为了C++。C++导入4个新的转型操作符(cast operators):
static_cast
、const_cast
、dynamic_cast
和reinterpret_cast
。 - static_cast具有与C旧式转型相同的威力与意义,以及相同的限制。static_cast不能移除表达式的常量性(constness),因为有const_cast。
- 其他新式C++转型操作符适用于更集种(范围更狭窄)的目的。const_cast用来改变表达式中的常量性(constness)或变易性(volatileness)。常见的用途在于将某个对象的常量性去掉。
- dynamic_cast用来执行继承体系中“安全的向下转型或跨转型动作”。它不能应用在缺乏虚函数的类型身上。
- reinterpret_cast这个于编译平台有关,基本不具移植性。它的常用用途是转换“函数指针”类型:
1
2
3
4
5typedef void (*FuncPtr)();
FuncPtr funcPtrArray[10];
int doSomething();
funcPtrArray[0] = &doSomething; // error
funcPtrArray[0] = reinterpret_cast<FuncPtr>(&doSomething); //ok
条款3:绝对不要以多态(polymorphically)方式处理数组
- 继承(inheritance)的最重要性质之一是可以通过指向base class objects的pinters或references来操作derived class objects。我们说它行为是多态的(polymorphically)。它也允许以上形成的数组。但这不值得沾沾自喜,因为它几乎不会如预期般运作。 在C++语言规范中说,通过base class指针删除一个由derived classes objects构成的数组,其结果未定义。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28class BST { ... };
class BalancedBST : public BST { ... };
void printBSTArray(std::ostream &s, const BST array[], int numElements)
{
for (int i = 0; i < numElements; ++i)
s << array[i]; // 假设BST objects有个operator<<可用
}
BST BSTArray[10];
printBSTArray(std::cout, BSTArray, 10); // ok
// 编译器可能会被误导,它假设数组元素大小是BST的大小,通常derived classes比base classes大。
// 那么会发生不可预期的事情??
BalancedBST bBSTArray[10];
printBSTArray(std::cout, bBSTArray, 10);
void deleteArray(std::ostream &logStream, BST array[])
{
logStream << "Deleting array at address "
<< stati_cast<void*>(array) << '\n';
delete []array;
}
BalancedBST &balTreeArray = new BalancedBST[50];
deleteArray(std::cout, balTreeArray);
// delete []array会产生这样的代码
/*
for (int i = the number of elements in the array -1; i >= 0; --i)
array[i].BST::~BST(); // 调用array[i]的destructor
*/
条款4:非必要不提供default constructor
所谓default constructor是C++一种无中生有的方式。constructors用来将对象初始化,所以default constructors的意思是在没有任何外来信息的情况将对象初始化。然而有些对象如果没有外来信息,是无法初始化的,可能产生的对象没有意义。
如果class缺乏一个default constructor,使用class时会有限制,第一个问题在产生数组的时候。例如:
1
2
3
4
5
6
7
8class EquipmentPiece
{
public:
EquipmentPiece(int IDNumber);
...
};
EquipmentPiece bestPieces[10]; // error
EquipmentPiece *bestPieces = new EquipmentPiece[10]; // error在产生数组的时候,一般而言没有任何方法可以为数组中的对象指定constructor自变量。有三个方法可以解决这个束缚:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// 第一个方式是使用non-heap数组
// 不幸的是此法无法延伸至heap数组
int ID1, ID2, ID3, ..., ID10;
...
EquipmentPiece bestPieces[] =
{
EquipmentPiece(ID1),
EquipmentPiece(ID2),
EquipmentPiece(ID3),
...,
EquipmentPiece(ID10)
};
// 更一般化的做法是使用“指针数组”而非“对象数组
// 此法有两个缺点。
// 第一,必须记得此数组所指的所有对象删除,否则会出现resource leak(资源泄露)
// 第二,需要足够大的内存总量,需要一些空间来放置指针和一些空间来放置EqupmentPiece objects
typedef EquipmentPiece *PEP;
PEP bestPieces[10]; // ok,不需要调用ctor
PEP *bestPieces = new PEP[10]; // ok
for (int i = 0; i < 10; ++i)
bestPieces[i] = new EquipmentPiece(ID number);过度使用内存这个问题可以避免:
1
2
3
4
5
6
7// 分配足够的raw memory
void *rawMemory = operator new[](10 * sizeof(EquipmentPiece));
// 让bestPieces指向此块内存,将这块内存视为一个EquipmentPiece数组
EquipmentPiece *bestPiece = static_cast<EquipmentPiece*>(rawwMemory);
// 利用“placement new”构造这块内存中的objects
for (int i = 0; i < 10; ++i)
new(&bestPieces[i]) EquipmentPiece(ID number);placement new的缺点是,维护起来比较困难。此外,在数组内的对象结束生命时,以手动方式调用其destructors,最后还得调用operator delete[]的方式释放raw memory:
1
2
3
4
5// 相反顺序析构掉
for (int i = 0; i >= 0; --i)
bestPieces[i].~EquipmentPiece();
// free raw memroy
operator delete[](rwaMemory);classes缺乏default constructors带来的第二个缺点是:它们不适用于许多template-based container classes。对template而言,被实例化(instantiated)的目标类型必须有一个deault constructors。这是一个普遍的共同需求,那些templates内几乎总是会产生一个以“template类型参数”作为类型而架构起来的数组,例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16template<class T>
class Array
{
public:
Array(int size);
...
private:
T *data;
};
template<class T>
Array<T>::Array(int size)
{
// 数组中的每个元素都调用T::T()
data = new T[size];
...
}如果谨慎设计,可以消除对default constructor的需求。标准库中vector template(产生可扩展数组的各种classes)就不要求default constructor。
最后一个考虑点是virtual base classes。因为virtual base class constructors的自变量由最深层次的派生类的class提供。于是,一个缺乏default constructor的virtual base class,要求所有derived classes都必须知道、了解其意义,并且提供自变量。