zl程序教程

您现在的位置是:首页 >  后端

当前栏目

编程参考 - C++的析构函数的个数只能有一个

C++编程 函数 一个 个数 参考 只能 析构
2023-09-11 14:22:08 时间

C和C++的变量或对象内存生命周期管理区别

C里面的变量,按照生存周期来分,分为静态变量和局部变量。

静态变量是程序在启动时就分配了空间,并且其地址在程序执行中保持不变。静态变量按照访问范围来分,包括全局变量或模块内静态变量。

而局部变量,是函数里在栈上自动分配空间,每次调用函数时,局部变量分配的地址可能不同。

而堆空间的使用,使用单独的库函数来完成。

在C++多了类的使用,类的实例我们通常称之为对象。

关于对象的生存周期管理,和C有所不同。

C语言里,函数内部的局部变量,在函数退出执行后其生命周期就结束了。如果函数内创建了仅在函数内访问的堆指针,函数退出前要释放堆内存。

而C++中,函数内部定义的类对象,在函数退出执行后其生命周期也结束,但在对象的生命周期结束时,要调用析构函数。加入析构函数,方便程序员更好的管理类对象的生命周期,尤其是在类对象生命周期结束时,经常需要对类所使用的资源进行释放。比如,类里面使用了堆内存,那类对象消亡前就应该释放资源,而这部分处理就可以放在析构函数里,而不用另外操作,使类操作更方便统一。类对象里的普通成员,是不需要特殊处理的,比如整型变量,在类对象消亡时,这部分变量占用资源会直接释放。

使用new生成的堆里的类对象,原理一样,当释放这个类指针资源时,也会调用类的析构函数。类对象本身占用的资源比如整型变量等会释放,如果类对象内部还是用了其他资源比如堆指针,在类的析构函数里进行释放操作。使用new给类对象分配内存,此时调用类的构造函数,当类对象不需要时,使用delete进行资源释放,此时类的析构函数被调用。

综上,C++类对象的构造和析构(constructor & destructor 或者 ctor & dtor),在创建类对象和删除类对象时使用。在创建类对象时,可以有多种初始状态,所以对象的构造函数有参数,并且有多个构造函数对应不同的参数列表。

构造函数和析构函数的名字和类名一样,析构函数前面加了一个位取反符。没有返回值。

类的析构函数只能有一个

类对象析构时,此时已穷途末路,别无选择,此时类对象的状态是稳定可知的,不需要参数来干预,添加更多的析构函数配以不同参数列表显然作用不大,所以C++标准里就是一个类只有一个析构函数。

在创建对象时可以传入不同参数列表,由编译器来调用不同构造函数。而析构函数则由编译器自动调用,当类对象将要被消灭掉时调用。

析构函数也可以手动调用,但情况很少,比较特殊,这里不深入介绍。尤其是局部变量的类对象,如果手动调用析构函数,函数退出时会再调用一次,假如有资源要释放,可能会因为释放两次而出错,总之析构函数调用两次这种情况就已经是非正常的,要避免。如下所示:

#include <stdio.h>

class A

{

public:

~A(){printf("~A\n");}

};

int main()

{

A a;

a.~A();

return 0;

}

$ g++ -o main main.cpp

$ ./main

~A

~A

类的析构函数的访问权限

如果一个类的析构函数是private的,那么编译器没法自动调用其析构函数。在类对象析构时,就会报错。

#include <stdio.h>

class A

{

private:

    ~A(){printf("~A\n");}

};

int main()

{

    A a;

    return 0;

}

$ g++ -o main main.cpp

main.cpp: In function ‘int main()’:

main.cpp:14:4: error: ‘A::~A()’ is private within this context

   14 |  A a;

      |    ^

main.cpp:7:2: note: declared private here

    7 |  ~A(){printf("~A\n");}

      |  ^

而且,如果另一个类继承这个类,也会报错。

如果这个类的析构函数是protected的,那别的类可以继承它。

但编译器还是不能自动调用其析构函数,当这个类要析构时,会报错。

注意,当析构函数是private或protected时,外部无法调用析构函数,导致这个对象不能正常删除。但可以使用类的成员函数或友元来做这件事。这表示这个类的生命周期由其他类来负责。

同理,对构造函数也是一样,如果构造函数声明为private或protected,那么类对象则不能在外部创建,只有类的成员函数或友元才能创建类对象。

析构函数和virtual

C++支持多态,所以父类的虚构函数应该都用virtual来声明。

如下所示,如果基类的析构函数不声明为虚函数,则子类对象就不会调用子类的析构函数。

如果基类的析构函数声明为虚函数,则使用父类的指针访问子类对象,调用析构函数时,就会先调用子类析构函数,再调用基类析构函数。

#include <stdio.h>

class A

{

public:

~A(){printf("~A\n");}

};

class B : public A

{

public:

~B(){printf("~B\n");}

};

int main()

{

        A* a = new B();

        delete a;

        return 0;

}

$ g++ -o main main.cpp

$ ./main

~A

#include <stdio.h>

class A

{

public:

virtual  ~A(){printf("~A\n");}

};

class B : public A

{

public:

~B(){printf("~B\n");}

};

int main()

{

        A* a = new B();

        delete a;

        return 0;

}

$ g++ -o main main.cpp

$ ./main

~B

~A

注意,如果基类一个成员函数声明为virtual,则子类的同名函数默认为virtual,与是否加virtual修饰符无关。但这个virtual声明是向下传染的,不会影响到当前类的基类。

但析构函数是特殊的,虽然函数名不同,但基类的析构函数声明为virtual,则子类的析构函数也是virtual的,如上面的例子所示。

参考:

Herb Sutter - Publications