C++基础#18:构造函数和析构函数使用注意事项

内容简介:

在C++中,编译器自动会为一个类创建一个default构造函数、一个copy构造函数、一个赋值运算符函数(operator=),以及一个析构函数。本节主要看构造函数和析构函数在实际使用过程中的一些注意事项。

构造函数和析构函数使用注意事项: 

1). 构造函数和析构函数都不能用const来修饰,因为在初始化和释放时,它们总是对对象作些修改;

代码示例:

class Base {
public:
    int a;
    const Base(){cout<<"Base constuctor"<<endl;}//error: constructor cannot have a return type
    const virtual void fun1() {cout<<"Base::fun1"<<endl;} //success
    virtual void fun2() {cout<<"Base::fun2"<<endl;}
    virtual void fun3() {cout<<"Base::fun3"<<endl;}
    ~Base(){cout<<"Base destroy"<<endl;}
};

int main () {
  Base b;

  return 0;
}

以上代码编译发生错误,错误信息如下:

error: constructor cannot have a return type

    const Base(){cout<<"Base constuctor"<<endl;}

    ^~~~~~

可见,提示信息也明确说明,构造函数的返回类型是错误的,主要是因为在构造函数前用了const。

正确的方式应该是:

Base(){cout<<"Base constuctor"<<endl;}

2). 构造函数、析构函数和赋值函数(operator=)不能被继承。

3). 不要在构造函数和析构函数中调用虚函数(virtual function)。
在构造和析构期间不要调用虚函数,因为这类调用从不下降至派生类。由于基类构造函数的执行更早于派生类构造函数,当基类构造函数执行时派生类的成员变量尚未初始化,如果此期间调用的虚函数下降至派生类阶层,因为派生类的函数几乎都会调用派生类的成员变量,又因为这些成员变量尚未初始化。要求使用对象内部尚未初始化的成分是危险的代名词,所以C++不允许这样做。

看下面这个例子:

#include <iostream>

using namespace std;

class Base {
public:
    int a;
    Base(){
      cout<<"Base constuctor"<<endl;
      fun2();
    } 
    void callFun() {fun2();}
    const virtual void fun1() {cout<<"Base::fun1"<<endl;} //success
    virtual void fun2() {cout<<"Base::fun2"<<endl;}
    virtual void fun3() {cout<<"Base::fun3"<<endl;}
    ~Base(){cout<<"Base destroy"<<endl;}
};

class Derive : public Base{
public:
    int b;
    Derive(){
      cout<<"Derive constuctor"<<endl;
      fun2();
    } 
    virtual void fun2() {
      cout<<"Derive::fun2"<<endl;
    }
    ~Derive(){cout<<"Derive destroy"<<endl;}
};

int main () {
  Derive b;    //在父类中的构造函数中,调用了父类的fun2
  b.callFun(); //在父类中的callFun中,调用了子类的fun2,从而引起二义性。


  return 0;
}

运行结果:

Base constuctor

Base::fun2

Derive constuctor

Derive::fun2

Derive::fun2

Derive destroy

Base destroy

分析说明:

1)callFun是基类Base的成员函数,它调用了func2函数;

2)func2是基类中的虚函数,它被Base类的构造函数调用;

期望的结果:

func2函数是虚函数,我们期望在使用子类的实例(Derive b)调用到func2时,一定会调用到子类的func2函数。

但是,在上述代码中,func2却被父类调用了(在父类的构造函数中调用的),这是我们部期望看到的。

在构造和析构期间不要调用虚函数,因为这类调用从不下降至派生类。由于基类构造函数的执行更早于派生类构造函数,当基类构造函数执行时派生类的成员变量尚未初始化,如果此期间调用的虚函数下降至派生类阶层,因为派生类的函数几乎都会调用派生类的成员变量,但此时成员变量尚未初始化。

4). 别让异常逃离析构函数:
析构函数绝对不要吐出异常。虽然在语法上,C++并不禁止析构函数吐出异常,但不鼓励你这样做。因为析构函数是一个实例的消亡过程,在实例消亡时,一切都已结束,而抛出异常异味着还有未结束的事情,就会产生二义性,到底是结束还是未结束。

所以,如果一个析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后处里他们或结束程序;
如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(非析构函数)执行该操作。

5).编译器产生的析构函数是非虚函数,除非这个类的父类有一个虚析构函数。

6). 为多态基类声明虚析构函数
带有多态性质的基类应该声明一个虚析构函数,即如果一个类带有任何虚函数,它就应该拥有一个虚析构函数;
类的设计目的如果不是作为基类使用,或者不是为了实现多态,就不应该声明析构函数为虚函数。
C++指出,当派生类对象经由一个基类指针被删除,而该基类有一个非虚析构,那么结果是未定义的。实际上执行时通常发生的是对象的derived成员没有被销毁(派生类的析构函数没有被调用),但是其基类成员被销毁了,造成了资源泄漏。
所以,给基类一个虚析构函数,那么以后通过基类指针或者引用删除该派生类的时候,就会销毁整个对象。

示例代码:

#include <iostream>

using namespace std;

class Base {
public:
    int a;
    Base(){
      cout<<"Base constuctor"<<endl;
    } 
    void callFun() {fun2();}
    const virtual void fun1() {cout<<"Base::fun1"<<endl;} //success
    virtual void fun2() {cout<<"Base::fun2"<<endl;}
    virtual void fun3() {cout<<"Base::fun3"<<endl;}
    virtual ~Base(){cout<<"Base destroy"<<endl;}
};

class Derive : public Base{
public:
    int b;
    Derive(){
      cout<<"Derive constuctor"<<endl;
      fun2();
    } 
    virtual void fun2() {
      cout<<"Derive::fun2"<<endl;
    }
    ~Derive(){cout<<"Derive destroy"<<endl;}
};

int main () {
  Derive b;    
  b.callFun(); 


  return 0;
}

运行结果:

Base constuctor

Derive constuctor

Derive::fun2

Derive::fun2

Derive destroy

Base destroy

可见,父类的析构函数(打印Base destroy)总是能够被正确执行。 


确保一个实例对象能够正确被创建和销毁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

liranke

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值