(一)有子类继承的基类的析构函数必须是virtual
如果基类的析构函数不是虚函数那也不会报错,但是在工程上是非常危险的,方法实现了多态,析构也要实现多态
否则容易出现这种类似问题
实现一个基类A
class A
{
public:A();~A();int a1;
private:void* buf;
};
A::A():buf(malloc(16))
{cout << "Creat A buf " << endl;
}
A::~A()
{cout << "Drop A buf " << endl;free(buf);
}
B1和B2继承A类
class B1 : public A
{
public:B1();~B1();
private:void* ch_buf;
};
B1::B1() :ch_buf(malloc(16))
{cout << "Creat B1" << endl;
}
B1::~B1()
{free(ch_buf);cout << "Drop B1 " << endl;
}class B2 : public A
{
public:B2();~B2();
private:void* ch_buf;
};
B2::B2() : ch_buf(malloc(16))
{cout << "Creat B2" << endl;
}
B2::~B2()
{free(ch_buf);cout << "Drop B2 " << endl;
}
场景模拟,由于构造哪个类由传参决定,所以返回的是基类
A* Creat1(int c)
{if (c == 1)return new B1;if (c == 2)return new B2;return nullptr;
}void Drop1(A* tm)
{delete tm;
}int main(int argc, char* argv[])
{auto test = Creat1(2);Drop1(test);return 0;
}
此时就会出现问题,Drop函数里delete只调用了基类的析构函数,但是没有调用子类的析构函数,造成内存泄露
所以为了防止这种情况产生,将基类的虚构函数设置成virtual就能调用到子类的虚构函数
(二)没有子类继承的类应当设置成final,当基类的虚函数被设置成final,则该虚函数不能在子类被重写,类被设置final则无法继承
基类的虚函数被设置成final,则该虚函数不能在子类被重写
类被设置final则无法继承
(三)虚函数表指针、虚函数表、虚函数之间的关系
在构造函数构造对象时创建虚函数表,并将虚函数表指针指向虚函数表,虚函数表就是一个指针数组,存放的是虚函数的地址,所以虚函数表指针是一个指针数组指针,所以当一个类里有虚构函数的时候,这个类就会多出一个指针的大小 【(32位系统)4字节,(64位系统)8字节】
当一个子类实例化时会先调用父类的构造函数,此时会创建一个父类的虚函数表,然后在调用子类的构造函数此时会把父类的虚函数表复制给子类,注意:子类和父类的虚函数表是不同的地址,但是里面的虚函数表里的指针指向同一个地址
如果子类重写了父类的虚函数,则会将重写的函数地址替换掉从基类继承的虚函数地址。
(四)override的作用
当子类重写了父类的虚函数,即使不加override也不会报错,但是加上override能提示coder,这个函数是重写自父类中的。
加上override之后,我们在重写的时候,编译器会知道我们是在重写虚函数,如果我们函数的名称写错了,编译器就会报错。
(五)通过虚继承来避免多继承造成的二义性
还是上面的类 A ; B1 : public A ; B2 : public A;
如果此时再来一个类继承自B1 和 B2
class C final:public B1, public B2
{
public:int c1;
};
那我们要怎么知道c.A::a1
调用的是什么
通过地址打印可以看到
c.A::a1
、c.B1::a1
是同一个地址
c.B2::a1
是另一个地址
可是如果我们在继承的时候使用虚继承
class B1 : virtual public A
class B2 : virtual public A
此时就可以发现
c.A::a1
、c.B1::a1
、c.B2::a1
都是同一个地址