面向对象的三大特性是封装、继承、多态。C++中有静多态和动多态的概念:静多态就是在编译时决定,比如函数重载;动多态就是在运行时决定,具体使用就是通过基类对象的指针或引用指向继承类对象,在运行时实现动态绑定,底层实现就是通过虚函数机制。
C++中动多态实现条件:
- 有继承关系
- 有虚函数覆写(override)
- 基类的指针或引用指向继承类
下面就不同的继承关系详解虚函数机制:
单继承
C++类定义中当定义用虚函数成员时就会触发虚函数机制,在类对象模型中就会添加一个虚表指针(vptr),指向虚函数表(vtable),虚函数表中存放着虚函数地址,放置顺序一般按照定义顺序,虚表指针的位置为了效率一般放置在类对象模型的起始位置。
#include <iostream>
using namespace std;
class Base
{
public:
virtual void foo(void)
{
cout << "Base::foo()" << endl;
}
virtual void bar(void)
{
cout << "Base::bar()" << endl;
}
};
typedef void (*pFunc)(void);
int main()
{
Base b;
cout << "sizeof(b) -> " << sizeof(b) << endl;
cout << "b address -> " << &b << endl;
cout << "b vtable address -> " << (int **)*(int *)&b << endl;
cout << " b::foo address -> " << ((int **)*(int *)&b)[0] << endl;
cout << " b::bar address -> " << (((int **)*(int *)&b))[1] << endl;
((pFunc)(((int **)*(int *)&b)[0]))();
((pFunc)(((int **)*(int *)&b)[1]))();
return 0;
}
运行结果:
在一般单继承中继承类的虚函数表中,先放置的是基类的虚函数,然后放置继承类中的虚函数,对于继承类中有覆写基类虚数的,继承类中基类虚函数部分会替换成继承类覆写的虚函数。
测试代码:
#include <iostream>
using namespace std;
class Base
{
public:
virtual void foo(void)
{
cout << "Base::foo()" << endl;
}
virtual void bar(void)
{
cout << "Base::bar()" << endl;
}
};
class Derive : public Base
{
public:
void foo(void) override
{
cout << "Derive::f()" << endl;
}
virtual void f(void)
{
cout << "Derive::f()" << endl;
}
};
typedef void (*pFunc)(void);
int main()
{
Base b;
Derive d;
cout << "sizeof(b) -> " << sizeof(b) << endl;
cout << "b address -> " << &b << endl;
cout << "b vtable address -> " << (int **)*(int *)&b << endl;
cout << " b::foo address -> " << ((int **)*(int *)&b)[0] << endl;
cout << " b::bar address -> " << (((int **)*(int *)&b))[1] << endl;
((pFunc)(((int **)*(int *)&b)[0]))();
((pFunc)(((int **)*(int *)&b)[1]))();
cout << "sizeof(d) -> " << sizeof(d) << endl;
cout << "d address -> " << &d << endl;
cout << "d vtable address -> " << (int **)*(int *)&d << endl;
cout << " d::foo address -> " << ((int **)*(int *)&d)[0] << endl;
cout << " d::f address -> " << (((int **)*(int *)&d))[1] << endl;
((pFunc)(((int **)*(int *)&d)[0]))();
((pFunc)(((int **)*(int *)&d)[1]))();
return 0;
}
运行结果:
多继承
一般多继承
一般多继承中,继承类的类对象模型中会在对象地址起始位置连续放置与基类个数想当的虚表指针,分别指向不同基类的虚函数表,对于继承类中的虚函数放置在第一个基类虚函数表里,继承类中有覆写基类虚函数的在虚函数表中替换基类虚函数。
测试程序:
#include <iostream>
using namespace std;
class Base1
{
public:
virtual void foo1(void)
{
cout << "Base1::foo1()" << endl;
}
virtual void bar1(void)
{
cout << "Base1::bar1()" << endl;
}
};
class Base2
{
public:
virtual void foo2(void)
{
cout << "Base2::foo2()" << endl;
}
virtual void bar2(void)
{
cout << "Base2::bar2()" << endl;
}
};
class Derive : public Base1, public Base2
{
public:
void foo1(void) override
{
cout << "Derive::foo1()" << endl;
}
void bar2(void) override
{
cout << "Derive::bar2()" << endl;
}
virtual void f(void)
{
cout << "Derive::f()" << endl;
}
};
typedef void (*pFunc)(void);
int main()
{
Derive d;
cout << "sizeof(d) -> " << sizeof(d) << endl;
cout << "d address -> " << &d << endl;
cout << "d->Base1 vtable address -> " << (int **)*(int *)&d << endl;
cout << " Derive::foo1 address -> " << ((int **)*(int *)&d)[0] << endl;
cout << " Base1::bar1 address -> " << ((int **)*(int *)&d)[1] << endl;
cout << " Derive::f address -> " << ((int **)*(int *)&d)[2] << endl;
((pFunc)(((int **)*(int *)&d)[0]))();
((pFunc)(((int **)*(int *)&d)[1]))();
((pFunc)(((int **)*(int *)&d)[2]))();
cout << "d->Base2 vtable address -> " << (int **)*((int *)&d+1) << endl;
cout << " Base2::foo2 address -> " << ((int **)*((int *)&d+1))[0] << endl;
cout << " Derive::bar2 address -> " << ((int **)*((int *)&d+1))[1] << endl;
((pFunc)(((int **)*((int *)&d+1))[0]))();
((pFunc)(((int **)*((int *)&d+1))[1]))();
return 0;
}
重复多继承
测试程序:
#include <iostream>
using namespace std;
class Base
{
public:
virtual void foo(void)
{
cout << "Base::foo()" << endl;
}
virtual void bar(void)
{
cout << "Base::bar()" << endl;
}
};
class Base1 : public Base
{
public:
virtual void foo1(void)
{
cout << "Base1::foo1()" << endl;
}
virtual void bar1(void)
{
cout << "Base1::bar1()" << endl;
}
};
class Base2 : public Base
{
public:
virtual void foo2(void)
{
cout << "Base2::foo2()" << endl;
}
virtual void bar2(void)
{
cout << "Base2::bar2()" << endl;
}
};
class Derive : public Base1, public Base2
{
public:
virtual void f(void)
{
cout << "Derive::f()" << endl;
}
};
typedef void (*pFunc)(void);
int main()
{
Derive d;
cout << "sizeof(d) -> " << sizeof(d) << endl;
cout << "d address -> " << &d << endl;
cout << "d->Base1 vtable address -> " << (int **)*(int *)&d << endl;
cout << " Base::foo address -> " << ((int **)*(int *)&d)[0] << endl;
cout << " Base::bar address -> " << ((int **)*(int *)&d)[1] << endl;
cout << " Base1::foo1 address -> " << ((int **)*(int *)&d)[2] << endl;
cout << " Base1::bar1 address -> " << ((int **)*(int *)&d)[3] << endl;
cout << " Derive::f address -> " << ((int **)*(int *)&d)[4] << endl;
((pFunc)(((int **)*(int *)&d)[0]))();
((pFunc)(((int **)*(int *)&d)[1]))();
((pFunc)(((int **)*(int *)&d)[2]))();
((pFunc)(((int **)*(int *)&d)[3]))();
((pFunc)(((int **)*(int *)&d)[4]))();
cout << "d->Base2 vtable address -> " << (int **)*((int *)&d+1) << endl;
cout << " Base::foo address -> " << ((int **)*((int *)&d+1))[0] << endl;
cout << " Base::bar address -> " << ((int **)*((int *)&d+1))[1] << endl;
cout << " Base2::foo2 address -> " << ((int **)*((int *)&d+1))[2] << endl;
cout << " Base2::bar2 address -> " << ((int **)*((int *)&d+1))[3] << endl;
((pFunc)(((int **)*((int *)&d+1))[0]))();
((pFunc)(((int **)*((int *)&d+1))[1]))();
((pFunc)(((int **)*((int *)&d+1))[2]))();
((pFunc)(((int **)*((int *)&d+1))[3]))();
return 0;
}
运行结果:
Derive间接继承Base两次,分别通过Base1和Base2。
虚拟多继承
对于上面的重复多重继承中,Base1继承至Base,Base2继承至Base,然后Derive又多继承至Base1和Base2,这样的话,Derive中会有两份抽象基类Base的拷贝,这种继承方式在有些场景下就不能用,比如我们的IO标准库中istream和ostream分别继承至抽象基类base_ios,然后iostream又多继承至istream和ostream,从而将间接继承base_ios两次,而实际上iostream读写操作希望在同一个缓冲区工作,所以这种继承方式无法实现共享。
因此C++中引入了虚继承,istream和ostream都虚继承至base_ios,告诉编译器我们要共享base_ios,这样iostream中就只有一份base_ios。这种继承关系又称为砖石型继承关系。
测试程序:
#include <iostream>
using namespace std;
class Base
{
public:
virtual void foo(void)
{
cout << "Base::foo()" << endl;
}
virtual void bar(void)
{
cout << "Base::bar()" << endl;
}
};
class Base1 : public virtual Base
{
public:
virtual void foo1(void)
{
cout << "Base1::foo1()" << endl;
}
virtual void bar1(void)
{
cout << "Base1::bar1()" << endl;
}
};
class Base2 : public virtual Base
{
public:
virtual void foo2(void)
{
cout << "Base2::foo2()" << endl;
}
virtual void bar2(void)
{
cout << "Base2::bar2()" << endl;
}
};
class Derive : public Base1, public Base2
{
public:
virtual void f(void)
{
cout << "Derive::f()" << endl;
}
};
typedef void (*pFunc)(void);
int main()
{
Derive d;
cout << "sizeof(d) -> " << sizeof(d) << endl;
cout << "d address -> " << &d << endl;
cout << "d->Base1 vtable address -> " << (int **)*(int *)&d << endl;
cout << " Base::foo address -> " << ((int **)*(int *)&d)[0] << endl;
cout << " Base::bar address -> " << ((int **)*(int *)&d)[1] << endl;
cout << " Base1::foo1 address -> " << ((int **)*(int *)&d)[2] << endl;
cout << " Base1::bar1 address -> " << ((int **)*(int *)&d)[3] << endl;
cout << " Derive::f address -> " << ((int **)*(int *)&d)[4] << endl;
((pFunc)(((int **)*(int *)&d)[0]))();
((pFunc)(((int **)*(int *)&d)[1]))();
((pFunc)(((int **)*(int *)&d)[2]))();
((pFunc)(((int **)*(int *)&d)[3]))();
((pFunc)(((int **)*(int *)&d)[4]))();
cout << "d->Base2 vtable address -> " << (int **)*((int *)&d+1) << endl;
cout << " Base2::foo2 address -> " << ((int **)*((int *)&d+1))[2] << endl;
cout << " Base2::bar2 address -> " << ((int **)*((int *)&d+1))[3] << endl;
((pFunc)(((int **)*((int *)&d+1))[2]))();
((pFunc)(((int **)*((int *)&d+1))[3]))();
return 0;
}
运行结果:
虚函数常见问题
析构函数可以为虚函数吗?
可以,在存在有虚函数覆写的父子类中,当使用父类的指针或引用指向之类时,父类的析构函数如果不为虚函数,那么delete父类的指针时,子类的析构函数不能够调用,会发生内存泄露。一般情况下我们把作为基类的析构函数定义为虚析构函数。注意编译器默认实现的析构函数不是虚函数,避免了虚函数表带来的开销以及和C语言类型的兼容。为什么虚函数必须是类的成员函数?
虚函数的目的是为了实现多态,多态发送在父子类中,在类外定义虚函数毫无用处,编译不过。构造函数可以定义为虚函数吗?
不可以,因为如果构造函数定义为虚函数,那么它将在运行期被构造出来,而运行期需要对象已经建立起来,构造函数的工作就是为了实现对象的建立,没有构建完成的对象上也不能实现多态机制。构造函数同时承担着虚函数表的建立,如果它本身都为虚函数,那么怎么能确保虚函数表的建立成功?基类构造函数和析构函数中有虚函数会有什么情况?
基类构造函数中有虚函数时虚函数不起作用,将调用局部版本,析构函数中同样也是调用局部版本。但是原因不同,构造函数中是因为还没有派生类的信息,析构函数中是因为继承类中的信息不可靠了。注意父子类中构造函数和析构函数过程相反,继承类中先构造基类对象,在构造子类对象,析构过程正好相反。纯虚函数的作用
纯虚函数能够更好的实现接口,定义有纯虚函数的类为抽象基类的,不能实例化类对象,纯虚函数在类中只有声明,没有定义,需要继承类中自己实现方法。