C++虚函数构成多态详解

为什么需要虚函数

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
#include <iostream>
using namespace std;
//基类Base
class Base{
public:
void func();
void func(int);
};
void Base::func(){
cout<<"void Base::func()"<<endl;
}
void Base::func(int n){
cout<<"void Base::func(int)"<<endl;
}
//派生类Derived
class Derived: public Base{
public:
void func();
};
void Derived::func(){
cout<<"void Derived::func()"<<endl;
}
int main(){
Base *p = new Derived();
p -> func(); //输出void Based::func()
p -> func(10); //输出void Base::func(int)
return 0;
}

以这个简单的继承关系来分析,派生类与基类存在一个同名成员函数void fun() 在main函数中,声明基类指针指向派生类的对象,那么在调用p -> fun()会指向什么?
按照惯常的逻辑,指针指向派生类对象那应该调用的是派生类的成员函数和成员函数,但实际上输出的是基类的成员函数,也就是说,基类的指针无法直接访问派生类成员函数
那么如何让基类指针指向基类对象就使用基类的所有成员,指向派生类对象就使用派生类所有成员,也就是实现基类指针的多种形态,就是多态性
就要利用虚函数,要在基类成员函数前加上virtual关键字

1
virtual void func();

这样的话,上述代码

1
p -> func();  //输出void Derived::func()

C++虚函数的目的就是:可以通过基类指针对所有派生类(包括直接派生和间接派生)的成员变量和成员函数进行“全方位”的访问,尤其是成员函数。如果没有多态,我们只能访问成员变量。

虚析构函数的必要性

学习虚函数时,书本上提到析构函数有时必须要声明为虚函数,这个必须是什么意思,让我百思不得其解
在查阅资料后,才知道这个是一种典型的内存泄露的问题

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
40
41
42
#include <iostream>
using namespace std;
//基类
class Base{
public:
Base();
~Base();
protected:
char *str;
};
Base::Base(){
str = new char[100];
cout<<"Base constructor"<<endl;
}
Base::~Base(){
delete[] str;
cout<<"Base destructor"<<endl;
}
//派生类
class Derived: public Base{
public:
Derived();
~Derived();
private:
char *name;
};
Derived::Derived(){
name = new char[100];
cout<<"Derived constructor"<<endl;
}
Derived::~Derived(){
delete[] name;
cout<<"Derived destructor"<<endl;
}
int main(){
Base *pb = new Derived();
delete pb;
cout<<"=============="<<endl;
Derived *pd = new Derived();
delete pd;
return 0;
}

这个代码可以看到析构函数起到一个释放内存的作用,在对象的生命周期结束后会自动释放构造函数时分配的内存
可以看到main函数中用了两种方式创建派生类的对象

1
2
Base *pb = new Derived();
Derived *pd = new Derived();

那么运行结果是什么呢?

Base constructor
Derived constructor
Base destructor
==============
Base constructor
Derived constructor
Derived destructor
Base destructor

delete pb只调用了基类的析构函数,这样就导致了创建的100个char类型的内存空间无法释放,就是典型的内存泄漏

  1. 为什么delete基类指针没有调用派生类的析构函数?
    因为编译器会根据指针类型来确认要调用的对象,所以不管它指向基类对象还是派生类的析构函数,始终调用基类构造函数
  2. 为何delete派生类指针会同时调用两类的析构函数
    因为这个是派生类的指针,会自动匹配到派生类的析构函数,在调用派生类析构函数会自动调用基类的析构函数
    所以要声明虚析构函数

如果将基类的析构函数声明为虚函数,那么派生类的析构函数会成为虚函数,此时编译器就会忽略指针类型,会根据指针的指向来选择函数,这样一来指针都会指向派生类的对象,就能调用派生类的构造函数,继而调用基类的析构函数,这样就能解决内存泄露的问题

虚函数和虚继承的不同

虚函数和虚继承完全是两种概念,千万不能当成同一类

1
2
3
4
5
6
7
8
9
class Base {
int m_a;
};
class Left : public Base { };
class Right : public Base {
};
class Derived : public Left, public Right {
void seta(int a){m_a = a};//命名冲突
};

如果我要是在Derived类中使用seta()函数,就会报错,因为编译器不知道m_a从何继承而来,虚继承就可以解决命名冲突的问题,让派生类中只保留一份积累的成员变量
总结一下这两者的区别
虚继承:

  • 虚继承是解决C++中多继承引起的钻石问题(Diamond Problem)的一种机制。
  • 当一个类通过虚继承从多个基类继承时,它只会包含一个基类的实例,避免了在对象内存模型中重复包含相同基类成员的问题。
  • 虚继承在类继承列表中使用 virtual 关键字声明,指示编译器使用虚继承。
    虚函数:
  • 虚函数是基类中声明的函数,用来允许派生类提供特定的实现。这是多态性实现的关键机制之一。
  • 虚函数使得可以通过基类指针或引用调用派生类中重写的函数。
  • 虚函数在基类中使用 virtual 关键字声明,在派生类中可以被重新定义

C++虚函数构成多态详解
http://example.com/2024/06/12/C-虚函数构成多态详解/
作者
max
发布于
2024年6月12日
许可协议