Effective C++读书笔记 构造/析构/赋值运算

  • 对于一个空类,编译器会自动创建构造函数、拷贝构造函数、赋值运算符重载以及析构函数。(当然只有在这些函数在被调用的时候才会被编译器创建出来)如果我们在类中显示的声明了这些函数,编译器将不再自动生成这些函数。
  • 当类的成员变量中有引用类型或者有const修饰,编译器不会为该类生成赋值运算符重载函数,尽管该类并没有显示的声明赋值运算符重载。
  • 想要防止拷贝可以显示声明拷贝构造函数,并将其设为私有,并且,只有声明,没有具体实现。
  • 如果使用一个基类的指针指向一个派生类的对象,并且基类的析构函数没有声明为虚函数,那么就会造成未定义的结果,派生类的部分没有被销毁,从而导致内存泄漏。将基类的析构函数设为虚函数就能解决这个问题。
    虚表指向的是一个函数指针数组。
  • 建议将析构函数声明为虚函数,但并不是说建议将所有的析构函数都声明为虚函数。如果一个类有可能被继承,那么,建议将它的析构函数声明虚函数,可防止一个基类对象指向一个派生类对象,在析构的是导致内存泄漏。如果一个类确保不会被继承,那么,就没有必要将它的析构函数设为虚函数,因为有虚函数就会有虚表,就需要维护虚表,就会产生额外的开销。
  • 含有纯虚函数的类是抽象类,抽象类不能实例化,一般都是用于被继承的,可以将析构函数设置为纯虚函数,这样既可以保证得到一个抽象类,又不需要担心析构函数的问题。需要注意的是,将析构函数声明为纯虚函数后,还必须为这个纯虚析构函数提供定义
  • 析构函数中一定不要吐出异常。假设析构函数一次需要析构多个元素,但是在析构第一个元素的时候抛出了异常,但是其他的还需要析构,所以还会继续执行下去,但是 ,如果在析构第二个的时候也抛出了异常,此时就有了两个异常,对于C++而言就太多了,在两个异常同时存在的情况下,程序若不是结束执行就是导致不明确行为。所以一定不要在析构函数中抛出异常。
  • 如果在析构函数中抛出了异常怎么处理呢?

    1. 调用abort( ) 函数。abort()函数用于异常终止一个进程。它的做法就是首先解除进程对SIGABRT信号的阻止,然后向调用进程发送一个SIGABRT信号。abort()函数会导致所有的流被关闭和冲洗。
    2. 吞下产生的异常。一般来说,吞掉异常是一个坏主意,因为它压制了“某些动作失败”的重要信息。但是总比负担“草率结束进程”或者“不明确行为”带来的风险要好,但是要保证这种方法是有效的,那么首先就得保证程序能够继续可靠的进行,即使是在遭遇并且忽略一个错误之后。
    3. 对于那些有可能产生异常的操作,另外封装一个函数来执行他们,然后在析构函数里边对他们进行检测,如果没有处理,再在析构函数里边进行处理。这样就相当于提供了一次更早处理异常的机会。当然,如果两重保险都没有成功处理,那就只能退回到上边的两种方式中了。
  • 绝不要在构造函数和析构函数中调用虚函数。

    1. 在一个继承体系中,如果父类的构造函数中调用了虚函数,当创建一个派生类的时候,会先调用父类的构造函数,父类的构造函数执行期间,不会下降到派生类层,也就是说,在基类构造过程中,会把将要创建的这个对象当做一个基类来处理,不会把虚函数当做虚函数来对待。因为,如果在把它当做派生类的话,也就是说在调用虚函数的时候会下降到派生类层中,虚函数会取用本地的成员变量,也就是派生类的成员变量,但是此时派生类中那个的成员变量都没有初始化。这样就会导致不明确行为。所以,为了避免这种行为,编译器在调用基类的构造函数时会忽略派生类的存在,在派生类构造函数执行起来之前不会成为一个派生类对象。这也就代表着基类中调用虚函数根本就不会形成多态,反而还有产生不明确行为的风险。
    2. 在析构函数中调用虚函数也是同样的理解,在调用基类的析构函数的时候,编译器会把这个对象当做基类对象,只有在调用派生类的析构函数时才会被当做派生类的对象。 (这种理解仅限于在构造函数或者析构函数中调用了虚函数的情况)
  • 在对赋值运算符重载的时候要注意如下几个地方

    1. 传参时,尽量传要赋值对象的引用。
    2. 保证自己自己给自己赋值时不会出错,可以先做一个检测,如果是自己给自己赋值,可以直接返回。
    3. 赋值过程是深拷贝,所以需要另外开辟空间,而开辟空间就可能会失败,所以,在确保空间申请成功之前不能将原来的空间释放掉。
    4. 对于已经不用了的空间不能忘记释放。
  • 复制对象是,要复制对象的每一个成员。一般我们在实现的拷贝构造函数的时候不会忘记每一个成员,但是在我们已经将构造函数实现出来了,后来又发现自己给的类成员还不能满足自己的需求,然后又增加了成员变量,这个时候就有可能会忘记将新增的成员变量添加到复制函数中了。在大多数编译器中,你忘记了,编译器是不会提醒你的。
  • 在一个继承体系中,派生类的拷贝构造函数不仅要实现派生类特有成员的拷贝,要实现对基类成员的拷贝。基类成员变量通常是被声明为私有的,在派生类中直接对其进行赋值是没有权限的。所以需要我们调用基类中适当的拷贝函数
  • 拷贝构造函数和其他的拷贝函数的实现代码可能非常相似,为了避免代码的重复,在拷贝构造函数中调用其他拷贝函数或者在其他的拷贝构造函数中调用拷贝构造函数,这都有可能会出现一些意外的结果(比如说在一个为初始化的对象上做一些只对已经初始化了的对象才有意义的事情)。所以,如果是想要为了避免代码重复,我们可以将重复的代码放到一个新的成员函数里边去,供这两个拷贝函数调用。

猜你喜欢

转载自blog.csdn.net/guaiguaihenguai/article/details/81289740