More Effective C++ 基础议题(Basics)

基础议题

条款1:仔细区别pointers和references

​ 没有所谓的null reference,但可以有null pointer,这个事实意味着使用reference可能会比使用pointers更有效率(不需要测试其有效性)。因此,让我做下结论:当你知道你需要指向某个东西,而且绝不会改变其他东西,或是当你实现一个操作符而其语法需求由pointers达成,你就应该选择references。任何其他时候,请采用pointers。

条款2:最好使用C++转型操作符

​ 旧式几乎允许你将任何类型转换为任何其他类型,这是十分拙劣的,并且他们难以辨识。

C++导入4个新的转型操作符(cast operator)
  • static_cast

    与C旧式转型有着相同的意义,也有相同的限制(struct不能转为int,或double转为pointer),且不能移除表达式的常量性。

  • const_cast

    最常见的用途是将某个对象的常量性去除掉

  • dynamic_cast

    用来执行继承体系中的“安全的向下转型或跨系转型动作”,转型失败时,会以一个null指针(当转型对象是指针)或一个exception(当转型对象是reference)表现出来。只能用于继承体系之中。他无法应用在缺乏虚函数的类型身上,也不能改变类型的常量性。

  • reinterpret_cast

    最常用的是转换“函数指针”类型,但这应该避免使用,除非走投无路。

语法规则的演变:

过去习惯的形式:(type) expression

现在的形式:static_cast(expression)

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

​ 以一个简单的例子引入:

#include <iostream>

class A {
public:
    int a;
};
class B: public A {
public:
    int b;
};

int main() {
    std::cout << "The size of A is " << sizeof(A) << "," 
              << "The size of B is " << sizeof(B);
    return 0;
}

​ 运行结果:

The size of A is 4,The size of B is 8

​ 通过运行结果分析,derived classs 通常比其 base classes 有更多的data member,所以 derived classs objects 通常都比其 base class objects 来得大。

​ 现在考虑有个函数,用来打印A数组中的每一个A的内容:

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

​ 当将一个A对象数组传给此函数,没问题,但如果将一个B对象数组传给这个函数,将会发生不可预期的结果。array[i]其实是一个“指针算数表达式”的简写;它代表的其实是*(array+i)。array是个指针,每次+1操作,所偏移的字节数等于array所指对象的大小,在这个函数中,指的是A,由以上的推论分析,sizeof(A)不等于sizeof(B),所以会发生错误。

​ 以下也会发生类似的错误,原因一样:

void delete_array(ostream& logStream, A array[]) {
    delete[] array;
}

​ 当把B对象的数组传入上面的函数,delete[] array必须产生类似这样的代码:

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

​ 从而产生一样的错误。

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

​ 所谓 default constructor 的意思是在没有任何外来信息的情况将对象初始化。如果class缺乏一个default constructor,当你使用这个class时候便会有某些限制。就以下面的例子慢慢解析:

class EquipmentPiece {
public:
    EquipmentPiece(int IDNumber);
    ...;
}

EquipmentPiece bestPiece[10]; //错误!无法调用构造函数。
EquipmentPiece* bestPiece = new EquipmentPiece[10]; //错误!同样无法调用构造函数。

​ 有三个方法可以解决上面的问题,分别是non-heap数组,指针数组,raw memory & placemnet new

non-heap数组:
int ID1, ..., ID9, ID10;
...
EquipmentPiece bestPiece[10] = {
    EquipmentPiece(ID1),
    EquipmentPiece(ID2),
    EquipmentPiece(ID3),
    ...
    EquipmentPiece(ID10)
};

​ 不幸的是,该方法无法应用在heap数组上。

指针数组:
typedef EquipmentPiece* PEP; // PEP是个指向EquimentPiece的指针
PEP bestPieces[10]; // 很好,不需要调用ctor
PEP* bestPieces = new PEP[10]; // 没问题

​ 数组中的各指针可用来指向一个个不同的Equipment object

for (int i = 0; i < 10; ++i) {
    bestPieces[i] = new EquipmentPiece(IDNumber);
}

在这里插入图片描述

​ 此法有两个缺点。第一,必须记得将此数组所指的所有对象删除。第二,需要一些空间来放置指针,还需要一些空间用来放置EquipmentPiece objects

raw memory & placemnet new
void* raw_memory = operator new[](10*sizeof(EquipmentPiece));
// 让bestPieces指向此块内存,使这块内存
// 被视为一个EquipmentPiece数组
EquipmentPiece* bestPieces = static_cast<EquipmentPiece*>(raw_memory);
// 利用“placement new”构造这块
// 内存中的EquipmentPiece objects。
for (int i = 0; i < 10; i++) {
    new (&bestPiece[i]) EquipmentPiece(IDNumber);
}

​ 这项技术允许你在“缺乏default constructor”的情况下仍能产生对象数组;但并意味着你可以因此回避供给constructor自变量。

placement new的缺点:大部分程序员不熟悉它,难以维护,另外在数组对象的生命结束时,以手动方式调用其destructors,最后还得调用operator delete[]的方式释放raw memory(不能采用一般的数组删除语法)

​ 大部分添加default constructors是无意义的,添加会影响classes的效率。如果member functions必须测试字段是否真被初始化了,其调用者便必须为测试行为付出时间代价,并为测试代码付出空间代价,因为可执行文件和程序都变大了。万一测试结果为否定,对应的处理程序又需要一些空间代价。如果class constructors可以确保对象的所有字段都会被正确地初始化,上述所有成本便都可以免除。如果default constructor无法提供这种保证,那么最好避免让default constructor-s出现。虽然这可能会对classes的使用方式带来某种限制,但同时也带来一种保证:当你真的是用了这样的classes,你可以预期它们所产生的对象会被完全地初始化,实现上亦富有效率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值