1.关于vptr和vtbl
之前的学习已经了解到c++多态主要靠虚函数实现,如果说c++的class在实现上相比c的struct有什么开销的话,那么虚函数表(vtbl)的维护和每个对象实例里虚表指针(vptr)将是比较明显的开销。
对于如下三个类
class A {public: virtual void vfunc1() { cout << "A::vfunc1/n"; } virtual void vfunc2() { cout << "A::vfunc2/n"; } void func1() { cout << "A::func1/n"; } void func2() { cout << "A::func2/n"; }};class B : public A {public: virtual void vfunc1() { cout << "B::vfunc1/n"; } void funcb() { cout << "B::funcb/n"; }};class C : public B {public: virtual void vfunc1() { cout << "C::vfunc1/n"; } void funcc() { cout << "C::funcc/n"; }};非虚成员函数:A::func1(),A::func2(),B::funcb(),C::funcc()会单独在内存里存一份
虚成员函数:A::vfunc1(),A::vfunc2(),B::vfunc1(),C::vfunc1()也会单独存一份,但是这四个虚函数会由虚函数表来记录,由于这个例子里有三个类,因此内存里会有三份虚函数B::vfunc1(),A::vfunc2(),表,我们假设它们为A,B,C表。 A表里会有两个指针,分别指向A::vfunc1(),A::vfunc2()的地址,B表里两个指针,分别指向B::vfunc1(),A::vfunc2(),同理,C表里的指针指向C::vfunc1(),A::vfunc2()。
对于用基类指针new子类的情况:A *pa = new B; 这个实例对象里放的也是B类对应的虚函数表,因为编译器做了个向上转型(upcasting)。
其实理解了虚函数表在内存的形式后,调用虚函数的代码可以这么表示: (*(pa->vptr)[n])(pa) 因为第一个参数肯定是*this。
学习群里u6th9d当时给我们提供了一些虚函数相关考验题
Cat cat("cat"); Dog dog("dog"); Animal* pcat = &cat; Animal* pdog = &dog; std::cout << "L01: "; pcat->say(); std::cout << "L02: "; pdog->say(); std::cout << "L03: "; cat.say(); std::cout << "L04: "; dog.say(); void* tmp = ((void**)pcat)[0]; ((void**)pcat)[0] = ((void**)pdog)[0]; ((void**)pdog)[0] = tmp; std::cout << "L05: "; pcat->say(); std::cout << "L06: "; pdog->say(); std::cout << "L07: "; cat.say(); std::cout << "L08: "; dog.say();之前也提到,成员对象的第一个内容是虚表,因此中间那段就是交换了对象里的虚表指针,使得*pcat里的vptr指向Dog类的vtbl,*pdog的vptr指向Cat类的vtbl,结果:
L01: cat miaomiao~~L02: dog wangwang~~L03: cat miaomiao~~L04: dog wangwang~~L05: cat wangwang~~L06: dog miaomiao~~L07: cat miaomiao~~L08: dog wangwang~~L05和L06确实交换了,但是L07和L08并没有表,我当时没弄清楚,续表指针不是都变了吗?为什么调用的还是原来的函数呢?原因在于对动态绑定的理解
2.动态绑定
为了C++的多态性,是有动态绑定和静态绑定这两种说法的:
静态绑定:绑定的对象是静态类型,也就是编译期就能决定的,是确定的,不会更改的,比如 A a; a的内容虽然会在运行期发生改变,但是a就是a,这点是不会变的。
动态绑定:绑定的对象是动态类型,动态类型就是指在编译期无法决定的,因为它可能在运行期发生改变,比如指针:A* pa; pa可以在运行时重新指向其他对象,或者转型指向B类或者C类。
这边文章很好地总结了静态绑定和动态绑定:https://www.oschina.net/question/54100_20313
通过vptr和vtbl实现虚函数是基于动态绑定的,因此基于指针调用的虚函数pcat,pdog调用的函数会随着虚表指针的改变发生改变,但是用过普通对象直接调用,例如L07和L08,这是静态绑定,在编译期就已经决定了要调用的函数,因此不会改变。
新闻热点
疑难解答
图片精选