c++虚函数(续) | Nobilta's Blog
0%

c++虚函数(续)

c++中的虚函数除了写到的内容之外,还有一些用法,这次就把它们全部补齐吧。

虚析构函数

上篇文章中说到,析构函数也是可以声明为虚函数的,如果你打算允许通过基类指针调用对象的析构函数(通过delete函数,这样是合法的),就需要让基类的虚构函数变成虚函数,否则执行delete的结果将是不确定的。
来看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Base
{
public:
~Base();
};
Base::~Base()
{
cout << "Base destructor";
}
class Derived
{
public:
~Derived();
private:
int *p;
}
Derived::Derived()
{
p = new int(0);
}
Derived::~Derived()
{
cout << "Derived destructor";
delete *p;
}
void del(Base *obj)
{
delete obj;
}
int main()
{
Base *b = new Derived();
del(b);
return 0;
}
/*
输出结果:
Base destructor
*/

在这个例子中,我们想要的效果是通过del函数,释放掉指针指向的内存空间,但是输出结果确实只删除了基类对象。这是因为析构函数不是虚函数,所以在编译阶段编译器就要决定删除这个指针所指向的空间,由于它只知道现在的状态是基类的对象,所以它只能调用基类的析构函数,因为我们派生类的空间没有被释放,这就会导致内存泄漏。
而为了解决这个问题,我们只需要将基类和派生类析构函数定义为虚函数,这样在编译过程中,编译器就不会直接静态绑定基类的析构函数,而是在运行过程中,动态的执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Base
{
public:
virtual ~Base();
};
Base::~Base()
{
cout << "Base destructor";
}
class Derived
{
public:
virtual ~Derived();
private:
int *p;
}
Derived::Derived()
{
p = new int(0);
}
Derived::~Derived()
{
cout << "Derived destructor";
delete *p;
}
void del(Base *obj)
{
delete obj;
}
int main()
{
Base *b = new Derived();
del(b);
return 0;
}
/*
输出结果:
Derived destructor
*/

虚表与动态绑定

那么在运行的时候,虚函数是怎么实现动态绑定的呢?每个多态类都有一个虚表,这个虚表中有当前类各个虚函数的入口,而在每个对象中也都有指向当前类虚表的指针。具体实现过程如下:

  • 构造函数中为对象的虚指针赋值
  • 通过多态类型的指针或引用调用成员函数时,通过虚指针找到虚表,进而找到虚函数的入口
  • 最后通过该入口地址调用虚函数
    来一个图示(图源清华在线):

    纯虚函数和抽象类

    虚函数中还有一种特殊的存在,纯虚函数,这种函数没有自己本身的函数体,具体用法是这样的:
    1
    2
    //virtual + 函数 + =0
    virtual void fun() = 0;
    也正是因为这种函数没有函数体,所以拥有纯虚函数的类无法实例化为具体的对象,一般也把这样的类称之为抽象类
    那么既然这种类无法实例化为具体的对象,那它还有什么作用呢?抽象类更重要的是最为一种规范,它作为基类,为它的派生类规定了函数的写法,让代码更加规范,这样也充分的利用了虚函数动态绑定的特性。(虽然不能将抽象类实例化,但是可以使用抽象类的指针让它指向它的派生类等等)

override和final

在c++11中,在使用类的时候新增了两个关键字,override和final,它们在我们对类进行继承操作时可以起到很大的帮助

override

override是重载函数的关键字,当我们在一个派生类中想要对基类的函数进行重载时,可以加入override关键字,这样在编译的过程中,编译器会将你想要重载的函数看作是函数的重载,并且在基类中寻找同名的被重载函数,如果找不到则直接报错,虽然c++默认就带有继承功能,但是使用override之后可以避免很多因为函数名书写错误或由于函数是const类型而导致的重载失败。

final

当我们想保证一个类或其中函数的稳定性,让它不能被继承,我们就可以使用final关键字,当类加上final关键字,这个类将不能被继承,而当函数中的类加上final关键字,虽然这个类可以被继承,但是在继承类中不能对这个函数进行重载操作,否则会报错。

您的支持将鼓励我继续创作!