准备工作

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

  

道友可以自己将尝试每种情况下程序内存分布的情况,以便更清晰的认识,虚函数与虚继承。

C++之虚函数与虚继承详解的更多相关文章

  1. C++ 派生类函数重载与虚函数继承详解

    目录 一.作用域与名字查找 1.作用域的嵌套 2.在编译时进行名字查找 3.名字冲突与继承 4.通过作用域运算符来使用隐藏的成员 二.同名函数隐藏与虚函数覆盖 1.几种必须区分的情况 2.一个更复杂的 ...

  2. C++ 虚函数、纯虚函数、虚继承

    1)C++利用虚函数来实现多态. 程序执行时的多态性通过虚函数体现,实现运行时多态性的机制称爲动态绑定:与编译时的多态性(通过函数重载.运算符重载体现,称爲静态绑定)相对应. 在成员函数的声明前加上v ...

  3. C++ 子类继承父类纯虚函数、虚函数和普通函数的区别

    C++三大特性:封装.继承.多态,今天给大家好好说说继承的奥妙 1.虚函数: C++的虚函数主要作用是“运行时多态”,父类中提供虚函数的实现,为子类提供默认的函数实现.子类可以重写父类的虚函数实现子类 ...

  4. virtual之虚函数,虚继承

    当类中包含虚函数时,则该类每个对象中在内存分配中除去数据外还包含了一个虚函数表指针(vfptr),指向虚函数表(vftable),虚函数表中存放了该类包含的虚函数的地址. 当子类通过虚继承的方式从父类 ...

  5. 虚函数&纯虚函数&抽象类&虚继承

    C++ 虚函数&纯虚函数&抽象类&接口&虚基类   1. 多态 在面向对象语言中,接口的多种不同实现方式即为多态.多态是指,用父类的指针指向子类的实例(对象),然后通过 ...

  6. C++ 由虚基类 虚继承 虚函数 到 虚函数表

    //虚基类:一个类可以在一个类族中既被用作虚基类,也被用作非虚基类. class Base1{ public: Base1(){cout<<"Construct Base1!&q ...

  7. C++之易混淆知识点四---虚函数与虚继承

    C++面向对象中,虚函数与虚继承是两个完全不同的概念. 一.虚函数 C++程序中只要类中含有虚拟函数,编译程序都会为此类生成一个对应的虚拟函数跳转表(vtbl),该虚拟函数跳转表是一个又若干个虚拟函数 ...

  8. [c++] C++多态(虚函数和虚继承)

    转自:https://www.jianshu.com/p/02183498a2c2 面向对象的三大特性是封装.继承和多态.多态是非常重要的一个特性,C++多态基于虚函数和虚继承实现,本文将完整挖掘C+ ...

  9. c++虚函数和虚继承

    关键字virtual用于父类方法,如果传了一个子类对象,并且子类重写了父类的这个virtual方法,就会调用子类的方法.传谁就调用谁,这个就是多态.#include<iostream> u ...

随机推荐

  1. Hive UDF开发-简介

    Hive进行UDF开发十分简单,此处所说UDF为Temporary的function,所以需要hive版本在0.4.0以上才可以. Hive的UDF开发只需要重构UDF类的evaluate函数即可.例 ...

  2. Opencv 图片边缘检测和最小外接矩形

    #include "core/core.hpp" #include "highgui/highgui.hpp" #include "imgproc/i ...

  3. Solaris文件系统管理

    不同的操作系统使用不同类型的文件系统 1.文件(管理)系统:是用来对文件和目录进行管理.控制的数据结构的总称. Windows当中的文件系统: ntfs ,fat32 ,fat64 Solaris 当 ...

  4. django 运行python manage.py sqlall books 时报错 app has migration

    出现这个问题的原因是版本之前的不兼容,我用的django版本是1.8.6 而 这条python manage.py sqlall books 是基于django1.0版本的. 在django1.8.6 ...

  5. UVA 1541 - To Bet or Not To Bet(概率递推)

    UVA 1541 - To Bet or Not To Bet 题目链接 题意:这题题意真是神了- -.看半天,大概是玩一个游戏,開始在位置0.终点在位置m + 1,每次扔一个硬币,正面走一步,反面走 ...

  6. Android------Intent.createChooser

    Intent的匹配过程中有三个步骤,包含Action , category与data 的匹配. 假设匹配出了多个结果.系统会显示一个dialog让用户来选    择.例如以下图: 那么今天我们主要是解 ...

  7. D3js-API介绍【英】

    Everything in D3 is scoped under the d3 namespace. D3 uses semantic versioning. You can find the cur ...

  8. Attempting to write a row[5] in the range [0,394] that is already written to disk.

    我用POI操作excel写数据,然后就报这个错了 XSSFWorkbook workbook = new XSSFWorkbook(); SXSSFWorkbook sxssfWorkbook = n ...

  9. linux下启动和关闭weblogic(转载)

    在weblogic定义的域中可以找到如下命令: /[youHome]/domains/[yourDomain]/startWebLogic.sh /[youHome]/domains/[yourDom ...

  10. 在mac下搭建Apacheserver

    Apache作为最流行的Webserver端软件之中的一个.它的长处与地位不言而喻.以下介绍下在mac下搭建Apacheserver的步骤: (1)"前往" –>" ...