笔记:Effective C++

03.const

1.const的修饰,mutable可以释放变量的const属性

记住就近原则

另外:

void f1(const Widget *pw);

void f2(Widget const *pw);

以上两者是等价的。

2.迭代器是以指针为根据塑造出来的,所以迭代器的作用就像个T*指针。声明迭代器为const就像声明指针为const一样(T* const),表示迭代器本身不能被修改,即不能指向不同的东西,但它指向的值是可以改动的。如果你希望迭代器所指的东西不可被修改,那就需要使用const_iterator;

(1)const std::vector<int>::iterator iter = vec.begin(),这是T* const,指针不可以变,指向的内容可以变

(2)std::vector<int>::const_iterator cIter = vec.begin();这是const T*,指向的内容不可以改变

3.两个成员函数如果只是常量性不同,可以被重载。

4.函数后面加const

编译器会自动给每一个函数加一个this指针。在一个类的函数后面加上const后,就表明这个函数是不能改变类的成员变量的(加了mutable修饰的除外.实际上,也就是对这个this指针加上了const修饰。

04.确定对象被使用前先初始化

读取未初始化的值会导致不明确的行为,为此,永远在使用对象之前对其初始化。

内置类型(c part of c++)需要手动初始化,如:int x = 0;因为c++不保证初始化它们,对于(non-C parts of c++)会被进行默认的初始化

内置类型以为的,都是构造函数对其初始化。

C++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前。因此如果构造函数在函数体内进行赋值,之此前,已经调用了default构造函数进行初始化,这是多余的。

更加高效的做法是:成员初始化列表(member initialization list)来替代赋值操作。

c++有着十分固定的“成员初始化次序”: base classes 更早于其drived classes被初始化,而class的成员变量总是以其声明的次序被初始化,与初始化列表中成员的次序无关。

staic对象与stack和heap-based对象不同,它应当是在数据区。被声明为static的对象。直到程序结束时才被销毁。

为避免“跨编译单元之初始化次序”问题,最好以local static(定义在函数内static 对象)来替代non-local static对象,因为不保证他们初始化的次序


05:了解c++默默编写并调用哪些函数

一个class,如果你自己没有声明,编译器会为它声明一个copy构造函数,一个copy assignment操作符和一个析构函数。所以这些函数都是public且inline,如果你没有声明任何构造函数,编译器也会为你声明一个default构造函数,注意:编译器产生的析构函数是个no-virtual函数,如果class的base class声明了virtual析构函数,才会被声明为virtual

class Empty {};
//等价如下
class Empty{
public:
Empty() {...}
Empty(const Empty & rhs){...}
~Empty(){...}
Empty& operator=(const Empty& rhs){....}
}

一旦你声明了一个构造函数,编译器就不会再为它创建default构造函数。

引用(reference)为对象起了另外一个名字,引用必须初始化,初始化时,引用和它的初始值绑定在一起,而不是将初始值拷贝给引用。一旦初始化完成,引用将和它的初始值绑定,引用无法再绑定到另外一个对象,因此必须初始化。

因此当编译器面对“内含const成员”和“内含reference成员”时,将不会为其够找copy assignment,你必须自己定义copy assignment


06:若不想使用编译器自动生成的函数,就该明确拒绝

如果你不想使用编译器自动产生的copy构造函数和copy assignment操作,那么最好的方法就是“将成员函数声明为private且故意不实现它们”,编译器生成的是public函数,因此对象可以直接调用,声明为private,将无法调用,另外,由于你以及声明了自己的版本,编译器将不会在为你构造


07:为多态基类声明virtual析构函数

当drived class 对象经由一个base class指针被删除,而该base class带着non-virtual 析构函数,其结果未定义——实际调用的是base class的析构函数,只销毁base class部分,对象的derived成分没有被销毁。

解决的方法:任何class只要带有virtual函数都几乎确定应该有一个virtual析构函数。

但是:如果class不含有virtual函数,令其析构函数为virtual往往会带来额外的问题

class Point{
public:
    Point(int xCoord, int yCoord);
    ~Point();
private:
    int x, y;
};

没有带vitual,那么该clas Point可以被认为是一个64bits的对象。但如果Point的析构函数是virtual,那么就不再是64bits

为了实现virtual函数,对象必须携带某些信息,主要用来在运行期决定哪个virtual函数该被调用。由vptr(virtual table pointer)指针指出。vptr指向一个由函数指针构成的数据(vtbl:virtual table);每一个带有virtual函数的class都有一个相应的vtbl。

因此,内含virtual函数的class,其对象的体积会增加。这样Point对象不再能够塞入一个64bit的缓冲器。

结论:只有当class内含有至少 virtual函数,才为它声明virtual析构函数

class AWOV {
public:
    virtual ~AWOV() = 0;
};

这个class有一个pure virtual函数,所以它是个抽象class,又由于它有个virtual析构函数,因此不用担心上述的析构函数问题。但注意: 必须为这个pure virtual析构函数提供一份定义

AWOV::~AWOV(){}//pure virtual析构函数的定义
析构函数被调用的次序与构造函数被调用的次序刚好相反,析构函数:最深层次的派生(most drived)的那个class其析构函数最先被调用,然后是其每一个base class的析构函数被调用。


08:别让异常逃离析构函数

09:绝不在构造和析构过程中调用virtual函数

base class构造期间virtual函数绝不会下降到derived classes阶层。取而代之的是,对象的作为就像录属base类型一样。在base class构造期间,virtual函数不是virtual函数

理由:base class的构造函数的执行更早于derived class构造函数,如果此时下降到derived class,derived class中的成员变量还没有别构造,显然是错误的。


10:令operator=返回一个reference to *this

Widget& operator=(const Widget& rhs){
   ...
   return *this;
}
这是一个协议,不仅适合=,也适合+=, -=, *=等等


11:在operator=中处理“自我赋值”

指针,引用,常常会为一个对象带来一个别名,“有一个以上的方法指称某对象”,有时base class的reference或指针可以指向一个derived class对象,这也有可能是别名

那么赋值x=y就有可能是自我赋值。自我复制会带来一些不安全的操作,比如先释放x,然后为其赋值,明显错误。

<pre name="code" class="cpp">Widget& Widget::operator=(const Widget& rhs){
 Bitmap *pOrig = pb;
 pb = new Bitmap(*rhs.pb);
 delete pOrig;
 return *this;
}

 先检测是否为自我赋值操作,但更好的方式如上述 

12:复制对象时勿忘其每一个成分

如果你不使用编译器提供的copying函数,自己提供copying函数,那么当你的copying函数有明显的错误时,编译器也不会报错。

因此,如果你为class添加一个成员变量,你必须同时修改copying函数。

每当“为derived class 撰写copying函数”时,必须小心地复制其base class成分。那部分往往是private,应该让derived class的copying函数调用相应的base class函数。因此编写coying函数:

(1)复制所有local成员变量

(2)调用所有base classes内的适当的copying函数

13:以对象管理资源

把资源放进对象内,我们便可依赖C++的“析构函数自动调用机制”确保资源被释放.

auto_ptr是个“类指针对象”即“智能指针”,其析构函数自动对其所指对象调用delete。

void f() {
    std::auto_ptr<Inverstment> pInv(createIverstment());
    std::auto_ptr<Inverstment> pInv2(pInv1);//pInv2指向对象,pInv1被设为null
    pInv1 = pInv2;//pInv1指向对象,pInv2被设为null
}

auto_ptr被销毁时会自动删除它所指之物,所以一定要注意别让多个auto_ptr同时指向同一个对象。

注意:若通过copy构造函数或copy assignment操作符复制它们,它们会变成NULL,而复制所得的指针将取得资源的唯一拥有权。

tr1::shared_ptr是RCSP,所谓RCSP也是智能指针,持续追踪共有多少对象指向某笔资源,并在无人指向它时自动删除该资源。tr1::shared_ptr不会像auto_ptr那样出想copy的异常反应。

14:在资源管理类中小心copying行为

16:成对使用new和delete时要采取相同形式
当你使用new(动态生成一个对象时),有两件事情发生。第一,内存被分配出来。第二,针对次内存会有一个(或更多)构造函数被调用。

当使用delete时,也有两件事情会发生,第一,针对次内存会有一个(或者多个)析构函数被调用,然后内存才被释放。

数组所用的内存通常还包括“数组大小”的记录,以便需要知道调用多少次析构函数,单一的内存则没有这笔记录。

如果你new时调用了[ ],那么你在delete时,也要使用[ ],告诉编译器,它是一个数组,需要多次调用析构函数。如果new不使用[ ],delete也不应该使用


18:让接口容易被正确使用,不易被误用

除非有好的理由,否则应该尽量令你的types的行为与内置types一致。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值