准备工作
1、VS2012使用命令行选项查看对象的内存布局
微软的Visual Studio提供给用户显示C++对象在内存中的布局的选项:/d1reportSingleClassLayout。使用方法很简单,直接在[项目P]选项下找到“visual属性”后点击即可。切换到cpp文件所在目录下输入如下的命令即可
c1 [filename].cpp /d1reportSingleClassLayout[className]
其中[filename].cpp就是我们想要查看的class所在的cpp文件,[className]指我们想要查看的class的类名。(下面举例说明...)
虚继承和虚函数是完全无相关的两个概念。
虚继承是解决C++多重继承问题的一种手段,从不同途径继承来的同一基类,会在子类中存在多份拷贝。这将存在两个问题:
其一,浪费存储空间;
第二,存在二义性问题,通常可以将派生类对象的地址赋值给基类对象,实现的具体方式是,将基类指针指向继承类(继承类有基类的拷贝)中的基类对象的地址,但是多重继承可能存在一个基类的多份拷贝,这就出现了二义性。
虚继承可以解决多种继承前面提到的两个问题:
虚继承底层实现原理与编译器相关,一般通过虚基类指针和虚基类表实现,每个虚继承的子类都有一个虚基类指针(占用一个指针的存储空间,4字节)和虚基类表(不占用类对象的存储空间)(需要强调的是,虚基类依旧会在子类里面存在拷贝,只是仅仅最多存在一份而已,并不是不在子类里面了);当虚继承的子类被当做父类继承时,虚基类指针也会被继承。
实际上,vbptr指的是虚基类表指针(virtual base table pointer),该指针指向了一个虚基类表(virtual table),虚表中记录了虚基类与本类的偏移地址;通过偏移地址,这样就找到了虚基类成员,而虚继承也不用像普通多继承那样维持着公共基类(虚基类)的两份同样的拷贝,节省了存储空间。
在这里我们可以对比虚函数的实现原理:他们有相似之处,都利用了虚指针(均占用类的存储空间)和虚表(均不占用类的存储空间)。
虚基类依旧存在继承类中,只占用存储空间;虚函数不占用存储空间。
虚基类表存储的是虚基类相对直接继承类的偏移;而虚函数表存储的是虚函数地址。
补充:
1、D继承了B,C也就继承了两个虚基类指针
2、虚基类表存储的是,虚基类相对直接继承类的偏移(D并非是虚基类的直接继承类,B,C才是)
#if 0 //测试虚表的存在 #include <iostream> using namespace std; class A { int i = 10; int ia = 100; void func() {} virtual void run() { cout << "A::run()" << endl; } virtual void run1() { cout << "A::run1()" << endl; } virtual void run2() { cout << "A::run2()" << endl; } }; class B : public A { virtual void run() { cout << "B::run()" << endl; } virtual void run1() { cout << "B::run1()" << endl; } }; class C :public A { virtual void run() { cout << "C::run()" << endl; } virtual void run1() { cout << "C::run1()" << endl; } virtual void run3() { cout << "C::run3()" << endl; } }; class D :/*virtual*/ public A { virtual void run() { cout << "D::run()" << endl; } virtual void run1() { cout << "D::run1()" << endl; } virtual void run2() { cout << "D::run2()" << endl; } virtual void run3() { cout << "D::run3()" << endl; } }; int test() { cout << sizeof(A) << endl << sizeof(B) << endl << sizeof(C) << endl << sizeof(D) << endl; cout << sizeof(long long) << endl; //A * pA = new D; D d; //d.run(); typedef void(*Function)(void); int ** pVtable = (int **)&d; #if 0 int * pVtable = (int*)&d; int vtaleAdress = *pVtable; int * ppVtable = (int*)vtaleAdress; int func1 = *ppVtable; Function f1 = (Function)func1; f1() #endif //pVtable[0][0] for (int idx = 0; pVtable[0][idx] != NULL; ++idx) { Function f = (Function)pVtable[0][idx]; f(); } //cout << (int)pVtable[1] << endl; //cout << (int)pVtable[2] << endl; getchar(); return 0; } int main(void) { test(); return 0; } #endif
测试一、二:单个继承的不同情况
#if 0 // 测试一:单个虚继承,不带虚函数 // 虚继承与继承的区别 // 1. 多了一个虚基指针 // 2. 虚基类位于派生类存储空间的最末尾 // 测试二:单个虚继承,带虚函数 // 1.如果派生类没有自己的虚函数,此时派生类对象不会产生 // 虚函数指针 // 2.如果派生类拥有自己的虚函数,此时派生类对象就会产生自己本身的虚函数指针, // 并且该虚函数指针位于派生类对象存储空间的开始位置 // #pragma vtordisp(off) #include <iostream> using std::cout; using std::endl; class A { public: A() : _ia(10) {} //virtual void f() { cout << "A::f()" << endl; } private: int _ia; }; class B : virtual public A { public: B() : _ib(20) {} void fb() { cout << "A::fb()" << endl; } virtual void f() { cout << "B::f()" << endl; } #if 1 virtual void fb2() { cout << "B::fb2()" << endl; } #endif private: int _ib; }; int main(void) { cout << sizeof(A) << endl; cout << sizeof(B) << endl; B b; getchar(); return 0; } #endif
测试三:多重继承
// 测试三:多重继承(带虚函数) // 1. 每个基类都有自己的虚函数表 // 2. 派生类如果有自己的虚函数,会被加入到第一个虚函数表之中 // 3. 内存布局中, 其基类的布局按照基类被声明时的顺序进行排列 // 4. 派生类会覆盖基类的虚函数,只有第一个虚函数表中存放的是 // 真实的被覆盖的函数的地址;其它的虚函数表中存放的并不是真实的 // 对应的虚函数的地址,而只是一条跳转指令 #if 1 #pragma vtordisp(off) #include <iostream> using std::cout; using std::endl; class Base1 { public: Base1() : _iBase1(10) {} /*virtual*/ void f() { cout << "Base1::f()" << endl; } /*virtual*/ void g() { cout << "Base1::g()" << endl; } /*virtual*/ void h() { cout << "Base1::h()" << endl; } private: int _iBase1; }; class Base2 { public: Base2() : _iBase2(100) {} virtual void f() { cout << "Base2::f()" << endl; } /*virtual*/ void g() { cout << "Base2::g()" << endl; } /*virtual*/ void h() { cout << "Base2::h()" << endl; } private: int _iBase2; }; class Base3 { public: Base3() : _iBase3(1000) {} virtual void f() { cout << "Base3::f()" << endl; } /*virtual*/ void g() { cout << "Base3::g()" << endl; } /*virtual*/ void h() { cout << "Base3::h()" << endl; } private: int _iBase3; }; class Derived : virtual public Base1 //, virtual public Base2 //, public Base3 { public: Derived() : _iDerived(10000) {} void f() { cout << "Derived::f()" << endl; } /*virtual*/ void g1() { cout << "Derived::g1()" << endl; } private: int _iDerived; }; int main(void) { Derived d; Base1 b1; //Base1 *pBase1 = &b1; //Base2 * pBase2 = &d; //Base3 * pBase3 = &d; Derived * pDerived = &d; //pBase2->f(); cout << "sizeof(d) = " << sizeof(d) << endl; cout << "&Derived = " << &d << endl; // 这三个地址值是不一样的 //cout << "pBase1 = " << pBase1 << endl; //cout << "pBase2 = " << pBase2 << endl; // //cout << "pBase3 = " << pBase3 << endl; // getchar(); return 0; } #endif
测试四:钻石型继承
// 测试四:钻石型虚继承(菱形继承) //虚基指针所指向的虚基表的内容: // 1. 虚基指针的第一条内容表示的是该虚基指针距离所在的子对象的首地址的偏移 // 2. 虚基指针的第二条内容表示的是该虚基指针距离虚基类子对象的首地址的偏移 #if 0 #pragma vtordisp(off) #include <iostream> using std::cout; using std::endl; class B { public: B() : _ib(10), _cb('B') {} virtual void f() { cout << "B::f()" << endl; } virtual void Bf() { cout << "B::Bf()" << endl; } private: int _ib; char _cb; }; class B1 : virtual public B { public: B1() : _ib1(100), _cb1('1') {} virtual void f() { cout << "B1::f()" << endl; } #if 1 virtual void f1() { cout << "B1::f1()" << endl; } virtual void Bf1() { cout << "B1::Bf1()" << endl; } #endif private: int _ib1; char _cb1; }; class B2 : virtual public B { public: B2() : _ib2(1000), _cb2('2') {} virtual void f() { cout << "B2::f()" << endl; } #if 1 virtual void f2() { cout << "B2::f2()" << endl; } virtual void Bf2() { cout << "B2::Bf2()" << endl; } #endif private: int _ib2; char _cb2; }; class D : public B1, public B2 { public: D() : _id(10000), _cd('3') {} virtual void f() { cout << "D::f()" << endl; } #if 1 virtual void f1() { cout << "D::f1()" << endl; } virtual void f2() { cout << "D::f2()" << endl; } virtual void Df() { cout << "D::Df()" << endl; } #endif private: int _id; char _cd; }; int main(void) { D d; cout << sizeof(d) << endl; getchar(); return 0; } #endif
道友可以自己将尝试每种情况下程序内存分布的情况,以便更清晰的认识,虚函数与虚继承。