首页 > 编程 > C++ > 正文

[Boolan] C++第五周学习笔记

2019-11-08 01:46:41
字体:
来源:转载
供稿:网友

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,这是静态绑定,在编译期就已经决定了要调用的函数,因此不会改变。


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表

图片精选