快捷搜索:  汽车  科技

c++虚函数和纯虚函数的区别:程序员必须掌握

c++虚函数和纯虚函数的区别:程序员必须掌握Base construct Child construct Child::foo Base deconstruct可是,从输出信息能够看出,派生类 Child 的析构函数没有被调用,对于本例而言,new 出来的 buf 没有对应的 delete,势必会造成内存泄漏。Base *pb = new Child(); pb->foo(); delete pb;编译这段C 代码完全没有问题,运行也不会报错,输出如下:Child c; c.foo();我们直接使用 Child 实例化一个对象 c,调用 c.foo(),此时得到如下输出:Base construct Child construct Child::foo Child deconstruct delete buf Base deconstruct一切尽在预料中。虽说对象 c 调用 foo() 的输出完全符合预计,但像上面那样

在阅读C 项目(caffe)源码时,发现不少基类不仅把常规的成员函数定义成虚函数(virtual),也会把析构函数定义为虚函数,结合前面几节的介绍,稍稍思考下,这样做的确是有原因的,本文将结合C 代码实例尝试探讨下。

c++虚函数和纯虚函数的区别:程序员必须掌握(1)

为什么要把析构函数定义为虚函数?

常规

随便写一段C 代码作为实例,在这个例子中,我们先不把析构函数定义为虚函数:

class Base { public: Base () { cout << "Base construct\n"; } ~Base() { cout << "Base deconstruct\n"; } virtual void foo() { cout << "Base::foo\n"; } char *buf; }; class Child: public Base { public: Child() { cout << "Child construct\n"; buf = new char[16]; } ~Child() { delete[] buf; cout << "Child deconstruct delete buf\n"; } void foo() { buf[0] = 3; cout << "Child::foo\n"; } };

这段代码的逻辑很简单,无非就是定义了两个类:类 Base 的成员函数 foo() 为虚函数,构造函数和析构函数都是常规函数,此外它还有个 public 的成员变量 buf。类 Child 则公开继承了 Base,因此它可以直接使用 Base::buf——在构造函数中 new 了一段内存,并且在析构函数 delete 掉它。

Child c; c.foo();

我们直接使用 Child 实例化一个对象 c,调用 c.foo(),此时得到如下输出:

Base construct Child construct Child::foo Child deconstruct delete buf Base deconstruct

一切尽在预料中。

不安全的问题

虽说对象 c 调用 foo() 的输出完全符合预计,但像上面那样定义类仍然是非常危险的做法。在这一节我们曾讨论过,父类指针可以调用派生类的重写函数,因此下面这两行C 代码也是合法的,请看:

Base *pb = new Child(); pb->foo(); delete pb;

编译这段C 代码完全没有问题,运行也不会报错,输出如下:

Base construct Child construct Child::foo Base deconstruct

可是,从输出信息能够看出,派生类 Child 的析构函数没有被调用,对于本例而言,new 出来的 buf 没有对应的 delete,势必会造成内存泄漏。

解决问题

要解决所谓的“不安全问题”,其实很简单,按照题目说的做——将基类的析构函数也定义为虚函数就可以了,请看修改后的C 代码:

class Base { public: Base () { cout << "Base construct\n"; } virtual ~Base() { cout << "Base deconstruct\n"; } ...

也即尽在基类 Base 的析构函数前加上 virtual 关键字,其他的所有代码都无需改动。现在再执行下面的这几行C 代码:

Base *pb = new Child(); pb->foo(); delete pb;

输出如下:

Base construct Child construct Child::foo Child deconstruct delete buf Base deconstruct

显然,此时派生类 Child 的析构函数也会被调用了,内存泄漏的问题被解决了。

小结

C 中的 virtual 关键字是非常好用,也是C 程序员必须掌握的关键字,其实,“不安全问题”出现的原因也是简单的:我们在静态类型与动态绑定一节中提到过,基本上只有涉及到 virtual 函数时,才会发生动态绑定,此时通过对象指针(pb)调用的函数由它指向的类(Child)决定,所以此时派生类 Child 的析构函数会被调用。如果基类 Base 的析构函数不是虚函数,那么对象指针(pb)调用的函数由其静态类型(Base)决定,也即调用的其实只是基类 Base 的析构函数而已。

猜您喜欢: