Virtual
概述
定义一个函数为虚函数,不代表函数为不被实现的函数。
定义为虚函数是为了允许用基类的指针来调用子类的这个函数。
定义纯虚函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数。
class Base
{
public:
virtual void fun1() { cout << "Base::fun1" << endl; }
virtual void fun2() = 0;
};
class A : public Base
{
public:
void fun1() { cout << "A::fun1" << endl; }
};
class B: public Base
{
public:
void fun1() { cout << "B::fun1" << endl; }
void fun2() { cout << "B::fun2" << endl; }
};
class C : public Base
{
public:
void fun2() { cout << "C:fun2" << endl; }
};
int main()
{
//Base* baseA = new A(); // ERROR
Base* baseB = new B();
baseB->fun1(); // B::fun1
baseB->fun2(); // B::fun2
Base* baseC = new C();
baseC->fun1(); // Base::fun1
baseC->fun2(); // C:fun2
delete baseB;
delete baseC;
}
虚表
每个包含了虚函数的类都包含一个虚表。
实例化一个对象,输出对象的地址和对象成员v的地址,如果对象的地址和x的地址相同,那么就意味着编译器把虚函数表放在了末尾,如果两个地址不同,那么就意味着虚函数表是放在最前面的。
class Base
{
public:
int v;
virtual void fun1() { cout << "Base::fun1" << endl; }
};
正好相差了4个字节(x86),因此编译器把生成的虚函数表放在了最前面。
Base* base = new Base();
cout << base << endl; // 0104F328
cout << &(base->v) << endl; // 0104F32C
获取虚表
class Base
{
public:
virtual void fun1() { cout << "Base::fun1" << endl; }
virtual void fun2() { cout << "Base::fun2" << endl; }
virtual void fun3() { cout << "Base::fun3" << endl; }
};
class A : public Base
{
public:
virtual void fun2() { cout << "A::fun2" << endl; }
};
Base* base = new Base();
long* p1 = (long*)base;// 转换
long* p2 = (long*)(*p2);
for (int i = 0; i < 3; ++i) {
cout << p2[i] << ' ';
}
cout << endl;// 10424985 10424980 10425435
A* a = new A();
long* p3 = (long*)a;
long* p4 = (long*)(*p3);
for (int i = 0; i < 3; ++i) {
cout << p4[i] << ' ';
}
cout << endl; // 10424985 10425585 10425435
可见第一个和第三个地址相同,而第二个不同。
通过函数指针调用函数
typedef void (*F)();
F f1 = (F)p2[0];
F f2 = (F)p2[1];
F f3 = (F)p2[2];
f1(); //Base::fun1
f2(); //Base::fun2
f3(); //Base::fun3
A* a = new A();
long* p3 = (long*)a;
long* p4 = (long*)(*p3);
F f4 = (F)p4[0];
F f5 = (F)p4[1];
F f6 = (F)p4[2];
f4(); //Base::fun1
f5(); //A::fun2`
f6(); //Base::fun3
虚表函数顺序
class Base
{
public:
virtual void fun1() { cout << "Base::fun1" << endl; }
virtual void fun2() { cout << "Base::fun2" << endl; }
virtual void fun3() { cout << "Base::fun3" << endl; }
};
class A : public Base
{
public:
virtual void funA() { cout << "A::funA" << endl; }
virtual void fun2() { cout << "A::fun2" << endl; }
};
虚函数表的顺序就是基类的虚函数表中的虚函数的顺序+自己的虚函数的顺序。
Base* a = new A();
long* p3 = (long*)a;
long* p4 = (long*)(*p3);
F f0 = (F)p4[0];
F f1 = (F)p4[1];
F f2 = (F)p4[2];
F f3 = (F)p4[3];
f0(); //Base::fun1
f1(); //A::fun2`
f2(); //Base::fun3
f3(); //A::funA
基类虚构
看下面的例子:
class Base1
{
public:
~Base1(){cout << "delete Base1\n";}
};
class Base2
{
public:
virtual ~Base2(){cout << "delete Base2\n";}
};
class A1:public Base1
{
public:
~A1(){cout << "delete A1\n";}
};
class A2:public Base2
{
public:
~A2(){cout << "delete A2\n";}
};
int main()
{
Base1* b1 = new A1;
A1* a1 = new A1;
Base2* b2 = new A2;
A2* a2 = new A2;
cout << "b1:\n";
delete b1;
cout << "a1:\n";
delete a1;
cout << "b2:\n";
delete b2;
cout << "a2:\n";
delete a2;
return 0;
}
输出如下:
b1:
delete Base1
a1:
delete A1
delete Base1
b2:
delete A2
delete Base2
a2:
delete A2
delete Base2
在实现多态时,要让基类虚构函数为虚函数,保证当用基类操作派生类,在析构时防止只析构基类而不析构派生类的状况发生,防止内存泄漏
多态实现
要实现虚函数和多态,要通过指针或者引用的方式。
- 一个类实例只会存放非静态成员变量和指向虚表的指针 vptr(如果存在),并且一般 vptr 在头部。
- 普通成员函数在内存某一位置,编译器都会对函数的名字进行修饰映射,让其变成唯一的函数名。
下面用一个例子演示这样的内存布局:
class Base
{
public:
Base() { cout << "create Base\n"; }
virtual ~Base() { cout << "delete Base\n"; }
virtual void virtualShow() { cout << "virtual show Base\n"; }
void showBase() { cout << "show Base\n"; }
char ch{ 'b' };
};
class A :public Base
{
public:
A() { cout << "create A\n"; }
~A() { cout << "delete A\n"; }
virtual void virtualShow() { cout << "virtual show A\n"; }
void showA() { cout << "show A\n"; }
char ch{ 'a' };
int integer{ 0 };
};
用指针或者引用可以实现多态,是因为可以让基类实例的 vptr 指向子类实例的 vptr 。
Base* pb = new A();
pb->virtualShow();
cout << pb->ch << endl;
delete pb;
输出:
create Base
create A
virtual show A
b
delete A
delete Base
引用类似:
A a;
Base& rb = a;
cout << rb.ch << endl;
rb.virtualShow();
输出:
create Base
create A
b
virtual show A
delete A
delete Base
但对于实际的对象:
Base b = A();
b.virtualShow();
cout << b.ch << endl;
输出:
create Base
create A
delete A
delete Base
virtual show Base
b
delete Base
可见生成了临时的 A 的实例,然后被删除了,并且 Base 的实例被删除了两次。
总结
- 每一个基类都会有自己的虚函数表,派生类的虚函数表的数量根据继承的基类的数量来定。
- 派生类的虚函数表的顺序,和继承时的顺序相同。
- 派生类自己的虚函数放在第一个虚函数表的后面,顺序也是和定义时顺序相同。
- 对于派生类如果要覆盖父类中的虚函数,那么会在虚函数表中代替其位置。
在编译的过程中编译器就为含有虚函数的类创建了虚函数表,并且编译器会在构造函数中插入一段代码,用来给虚函数指针赋值,虚函数表是在编译的过程中创建。
虚函数指针是基于对象的,对象在实例化的时候,虚函数指针就会创建。虚函数指针是在运行时创建。在实例化对象的时候会调用到构造函数,会执行虚函数指针的赋值代码,从而将虚函数表的地址赋值给虚函数指针。
普通函数是静态编译的,没有运行时多态,只会根据指针或引用的字面值类对象,调用自己的普通函数,是父类为子类提供的强制实现,在继承关系中,子类一般不重写父类的普通函数。
- 本文链接:https://morisa66.github.io/2021/03/04/Virtual/
- 版权声明:本博客所有文章除特别声明外,均默认采用 许可协议。