More Effective C++: 01基础议题

01:仔细区别 pointers 和 references

         1:没有所谓的null reference,但是可以将 pointer 设为null。由于 reference 一定得代表某个对象,C++ 因此要求 references 必须有初值,但是pointers 就没有这样的限制。

         没有所谓的 null reference 这个事实意味使用 references 可能会比使用 pointers更富效率。这是因为使用 reference 之前不需测试其有效性。

         2:Pointers 和 references 之间的另一个重要差异就是,pointers 可以被重新设值,指向另一个物件,reference 却总是指向它最初获得的那个物件。

 

02:最好使用C++转型操作符

         1:低阶转型动作,像goto一样地被视为程序设计上的贱民。尽管如此,某些情况下,转型可能是必要的。

         2:旧式的C转型方式,几乎允许你将任何型别转换为任何其他型别,这是十分拙劣的。更好的方式是每次转型都能够更精确地指明意图。旧式转型的第二个问题是它们难以辨识,旧式转型的语法形式是 (type) expression 这样的,不只是人眼难以辨识,诸如 grep 之类的工具也无法区分语法上极类似的一些非转型写法。

         3:为解决C旧式转型的缺点,C++ 导入四个新的转型运算符:static_cast, const_cast, dynamic_cast 和 reinterpret_cast。

         static_cast基本上拥有与C旧式转型相同的威力与意义,以及相同的限制。例如你不能够利用 static_cast 将一个 struct 转型为 int 或将一个double转型为pointer;

         const_cast最常见的用途就是将某个对象的常数性去除掉;

         dynamic_cast用来执行继承体系中“安全的向下转型或跨系转型动作”。也就是说你可以利用 dynamic_cast,将“指向  base class objects之pointers或references”转型为“指向 derived(或 sibling base)class objects之 pointers 或 references”,并得知转型是否成功。如果转型失败,会以一个null指针(当转型对象是指针)或一个exception(当转型对象是  reference)表现出来。

最后一个转型运算符是reinterpret_cast。这个运算符的转换结果几乎总是与编译平台息息相关。所以 reinterpret_casts 不具移植性。最常见的用途是转换“函数指针”。假设有一个函数指针数组:

typedef void (*FuncPtr)();
FuncPtr funcPtrArray[10];

现在希望将以下函数指针放进 funcPtrArray 中:

int doSomething();

 使用 reinterpret_cast,可以强迫编译器编译通过:

funcPtrArray[0] = reinterpret_cast<FuncPtr>(&doSomething);

 函数指针的转型动作,并不具移植性,某些情况下这样的转型可能会导至不正确的结果,所以应该尽量避免将函数指针转型。

 

03:绝对不要以多态方式处理数组

         假设有下面的代码:

class BST { ... };
class BalancedBST: public BST { ... };

void printBSTArray(ostream& s, const BST array[], int numElements)
{
    for (int i = 0; i < numElements; ++i) {
        s << array[i];
    }
}

BalancedBST bBSTArray[10];
printBSTArray(cout, bBSTArray, 10);

          上面的代码编译器不会报错,但是针对array[i],它的本质是*(array+i),编译器在编译期间必须知道数组中的对象大小。函数原型中,参数array声明为类型为BST的数组,所以编译器认为数组中的每个元素必然都是BST对象,然而实际上数组元素都是BalancedBST对象,这就有问题了。

        

         上述问题有时候会以一种更隐秘的方式出现:

void deleteArray(ostream& logStream, BST array[])
{
    delete [] array;
}
BalancedBST *balTreeArray = new BalancedBST[50];
deleteArray(cout, balTreeArray);

          delete数组时,数组中每一个元素的析构函数就会被调用,所以,delete []array这样的语句,会产生下面的代码:

for (int i = the number of elements in the array - 1; i >= 0; --i)
{
    array[i].BST::~BST();
}

          错误的原因与上面是一样的。

 

04:非必要不提供default constructor

         1:有许多对象,如果没有外来信息,就没有办法执行一个完全的初始化动作。例如一个用来表现联络簿字段的 class,如果没有获得外界指定的人名,产生出来的对象将毫无意义。

凡可以“合理地从无到有产出对象”的 classes,都应该内含 default constructors,而“必须有某些外来信息才能产出对象”的 classes,则不必拥有default constructors。

2:假定有一个NoDefault类,它没有定义自己的默认构造函数,却有一个接受一个string实参的构造函数。NoDefault没有默认构造函数,意味着它具有以下限制:

a:具有NoDefault成员的每个类的每个构造函数,必须通过传递一个初始的string值给NoDefault构造函数来显式地初始化 NoDefault成员。

b:编译器将不会为具有NoDefault类型成员的类合成默认构造函数。如果这样的类希望提供默认构造函数,就必须显式地定义,并且默认构造函数必须显式地初始化其NoDefault成员。

c:NoDefault类型不能用作动态分配数组的元素类型。

d:NoDefault类型的静态分配数组必须为每个元素提供一个显式的初始化式。

e:如果有一个保存NoDefault对象的容器,例如vector,就不能使用接受容器大小而没有同时提供一个元素初始化式的构造函数。

f:如果NoDefault作为virtual base classe,则要求其最底层的派生类--不管距离多么遥远--都必须知道其意义,从而提供NoDefault的构建自变量。

 

3:对于有些类而言,盲目定义default constructors返回会带来不好的影响。假设在某些公司,所有仪器设备都必须贴上一个识别号码;为这种用途而设计的类EquipmentPiece,如果其中没有供应适当的ID号码,将毫无意义,因此不应该定义默认构造函数。然而如果为其定义了:

class EquipmentPiece {
public:
    EquipmentPiece(int IDNumber = UNSPECIFIED);
...
private:
    static const int UNSPECIFIED;
    //  一个魔术数字,
    // 意味没有被指定 ID  值
};

 这种代码,肯定会使得该类内其他的成员函数变得复杂:因为允许一个无ID的EquipmentPiece对象能够存在,其他成员函数必须检查ID是否存在,这就造成了时间和空间上的代价。如果构造函数保证所有字段能正确的初始化,就不会出现这种问题。因此,这样的类就不应该有默认构造函数。

猜你喜欢

转载自www.cnblogs.com/gqtcgq/p/9464357.html
今日推荐