Effective C++的50条建议
2020年11月16日16:11:06
- 尽量用const和inline而不用#define
尽量用编译器而不用预处理。
- 尽量用<iostream>而不用<stdio.h>
scanf和printf很有用,但不是类型安全的,而且没有扩展性。
on the other hand,①有些iostream的操作实现起来要比相应的C stream效率要低,但不是对所有的iostream而言,而是一些特殊的实现;②在标准化的过程中,iostream库在底层做了很多修改,所以对那些要求最大可移植性的应用程序来说,会发现不同的厂商遵循标准的程度也不同;③iostream库的类有构造函数而<stdio.h>里的函数没有,在某些涉及到静态对象初始化顺序的时候,如果可以确认不会带来隐患,用标准C库更加简单实用。
- 尽量用new和delete而不用malloc和free
malloc和free(及其变体)产生问题的原因是因为它们太简单:它们不知道构造函数和析构函数。
- 尽量使用C++风格的注释
- 对应的new和delete要采用相同的形式
用new的时候会发生两件事:首先,内存会被分配,然后,为分配的内存调用一个或多个构造函数;用delete的时候,也有两件事发生,首先,为将被释放的内存调用一个或多个析构函数,然后释放内存。对于delete来说有这样一个重要的问题:内存中有多少个对象要被删除?答案决定了有多少个析构函数将被调用。
这个问题简单说来就是,要被删除的指针指向的是单个对象呢,还是对象数组?这只有你来告诉delete。如果你在delete时没有用括号,delete就会认为指向的是单个对象;否则,它就会认为指向的是一个数组:
string *stringPtr1 = new string;
string *stringPtr2 = new string100;
……
delete stringPtr1;
delete[] stringPtr2;
如果在stringPtr1前加了[]和stringPtr2前没有加[],结果都是不可预测的。解决这类问题的规则:如果你调用new时用了[],那么调用delete时也要用[],如果调用new时没有用[],那么调用delete时也不要用[].
- 析构函数里对指针成员调用delete
如果在析构函数里没有删除指针,它不会表现出很明显的外部症状。相反,它可能只是表现为一点微小的内存泄露,并且不断增长,最后吞噬了你的内存空间,导致程序夭折。
删除空指针是安全的(因为它什么也没做)。
当然对本条款的使用也不要绝对,比如,你不会用delete去删除一个没有用new来初始化的指针,而且,就像用智能指针对象时不用劳你来删除一样,你也永远不会去删除一个传递给你的指针。换句话说,除非类成员最初用了new,否则是不用在析构函数里用delete的。
- 预先准备好内存不够的情况
- 写operator new和operator delete时要遵循常规
要有正确的返回值;可用内存不够时要调用出错处理函数;处理好0字节内存请求的情况;还要避免不小心隐藏了标准形式的new。
处理零字节请求的技巧在于把它作为请求一个字节来处理。
- 避免隐藏标准形式的new
在类里定义了一个称为“operator new”的函数后,会不经意地阻止了对标准new 的访问。条款50 解释了为什么会这样,这里我们更关心的是如何想个办法避免这个问题。一个办法是在类里写一个支持标准 new 调用方式的operator new,它和标准new 做同样的事。这可以用一个高效的内联函数来封装实现。另一种方法是为每一个增加到 operator new 的参数提供缺省值(见条款24)。
- 如果写了operator new就要同时写operator delete
为什么有必要写自己的operator new和operator delete?为了效率。缺省的operator new和operator delete具有非常好的通用性,它的这种灵活性也使得在某种特定的场合下,可以进一步改善它的性能。尤其在那些需要动态的分配大量的但很小的对象的应用程序里,情况更是如此。
如果写了一个自己的内存分配程序,就要同时写一个释放程序。
- 为需要动态分配内存的类声明一个拷贝构造函数和一个赋值操作符
用delete去删除一个已经被删除的指针,其结果是不可预测的。
只要类里有指针时,就要写自己版本的拷贝构造函数和赋值操作符函数。在这些函数里,你可以拷贝那些被指向的数据结构,从而使每个对象都有自己的拷贝;或者你可以采用某种引用计数机制(见条款 M29)去跟踪当前有多少个对象指向某个数据结构。
对于有些类,当实现拷贝构造函数和赋值操作符非常麻烦的时候,特别是可以确信程序中不会做拷贝和赋值操作的时候,去实现它们就会相对来说有点得不偿失。照本条款的建议去做:可以只声明这些函数(声明为private 成员)而不去定义(实现)它们。这就防止了会有人去调用它们,也防止了编译器去生成它们。关于这个俏皮的小技巧的细节,参见条款27。
- 尽量使用初始化而不要在构造函数里赋值
从纯实际应用的角度来看,有些情况下必须用初始化。特别是 const 和引用数据成员只能用初始化,不能被赋值。
对有基类的对象来说,基类的成员初始化和构造函数体的执行发生在派生类的成员初始化和构造函数体的执行之前。