0%

1 基础议题

条款1:指针与引用的区别

  1. 当你知道你必须指向一个对象并且不想改变其指向时,或者在重载符并为防止不必要的语义误解时,你不应该使用指针。除此之外的情况下,则使用指针。

条款2:尽量使用C++风格的类型转换

  1. 将一个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_castconst_castdynamic_castreinterpret_cast
  2. static_cast具有与C旧式转型相同的威力与意义,以及相同的限制。static_cast不能移除表达式的常量性(constness),因为有const_cast。
  3. 其他新式C++转型操作符适用于更集种(范围更狭窄)的目的。const_cast用来改变表达式中的常量性(constness)或变易性(volatileness)。常见的用途在于将某个对象的常量性去掉。
  4. dynamic_cast用来执行继承体系中“安全的向下转型或跨转型动作”。它不能应用在缺乏虚函数的类型身上。
  5. reinterpret_cast这个于编译平台有关,基本不具移植性。它的常用用途是转换“函数指针”类型:
    1
    2
    3
    4
    5
    typedef void (*FuncPtr)();
    FuncPtr funcPtrArray[10];
    int doSomething();
    funcPtrArray[0] = &doSomething; // error
    funcPtrArray[0] = reinterpret_cast<FuncPtr>(&doSomething); //ok

条款3:绝对不要以多态(polymorphically)方式处理数组

  1. 继承inheritance)的最重要性质之一是可以通过指向base class objects的pinters或references来操作derived class objects。我们说它行为是多态的polymorphically)。它也允许以上形成的数组。但这不值得沾沾自喜,因为它几乎不会如预期般运作。
    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
    28
    class 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
    */
    在C++语言规范中说,通过base class指针删除一个由derived classes objects构成的数组,其结果未定义

条款4:非必要不提供default constructor

  1. 所谓default constructor是C++一种无中生有的方式。constructors用来将对象初始化,所以default constructors的意思是在没有任何外来信息的情况将对象初始化。然而有些对象如果没有外来信息,是无法初始化的,可能产生的对象没有意义。

  2. 如果class缺乏一个default constructor,使用class时会有限制,第一个问题在产生数组的时候。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    class 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);
  3. 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
    16
    template<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。

  4. 最后一个考虑点是virtual base classes。因为virtual base class constructors的自变量由最深层次的派生类的class提供。于是,一个缺乏default constructor的virtual base class,要求所有derived classes都必须知道、了解其意义,并且提供自变量。