C++之虚函数与虚继承详解
准备工作
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++之虚函数与虚继承详解的更多相关文章
- C++ 派生类函数重载与虚函数继承详解
目录 一.作用域与名字查找 1.作用域的嵌套 2.在编译时进行名字查找 3.名字冲突与继承 4.通过作用域运算符来使用隐藏的成员 二.同名函数隐藏与虚函数覆盖 1.几种必须区分的情况 2.一个更复杂的 ...
- C++ 虚函数、纯虚函数、虚继承
1)C++利用虚函数来实现多态. 程序执行时的多态性通过虚函数体现,实现运行时多态性的机制称爲动态绑定:与编译时的多态性(通过函数重载.运算符重载体现,称爲静态绑定)相对应. 在成员函数的声明前加上v ...
- C++ 子类继承父类纯虚函数、虚函数和普通函数的区别
C++三大特性:封装.继承.多态,今天给大家好好说说继承的奥妙 1.虚函数: C++的虚函数主要作用是“运行时多态”,父类中提供虚函数的实现,为子类提供默认的函数实现.子类可以重写父类的虚函数实现子类 ...
- virtual之虚函数,虚继承
当类中包含虚函数时,则该类每个对象中在内存分配中除去数据外还包含了一个虚函数表指针(vfptr),指向虚函数表(vftable),虚函数表中存放了该类包含的虚函数的地址. 当子类通过虚继承的方式从父类 ...
- 虚函数&纯虚函数&抽象类&虚继承
C++ 虚函数&纯虚函数&抽象类&接口&虚基类 1. 多态 在面向对象语言中,接口的多种不同实现方式即为多态.多态是指,用父类的指针指向子类的实例(对象),然后通过 ...
- C++ 由虚基类 虚继承 虚函数 到 虚函数表
//虚基类:一个类可以在一个类族中既被用作虚基类,也被用作非虚基类. class Base1{ public: Base1(){cout<<"Construct Base1!&q ...
- C++之易混淆知识点四---虚函数与虚继承
C++面向对象中,虚函数与虚继承是两个完全不同的概念. 一.虚函数 C++程序中只要类中含有虚拟函数,编译程序都会为此类生成一个对应的虚拟函数跳转表(vtbl),该虚拟函数跳转表是一个又若干个虚拟函数 ...
- [c++] C++多态(虚函数和虚继承)
转自:https://www.jianshu.com/p/02183498a2c2 面向对象的三大特性是封装.继承和多态.多态是非常重要的一个特性,C++多态基于虚函数和虚继承实现,本文将完整挖掘C+ ...
- c++虚函数和虚继承
关键字virtual用于父类方法,如果传了一个子类对象,并且子类重写了父类的这个virtual方法,就会调用子类的方法.传谁就调用谁,这个就是多态.#include<iostream> u ...
随机推荐
- linux下的C语言开发(网络编程)
http://blog.csdn.net/feixiaoxing/article/details/7259675 [ 声明:版权所有,欢迎转载,请勿用于商业用途. 联系信箱:feixiaoxing ...
- class文件结构浅析(2)
欢迎转载,转载需声明出处 ------------------ 请先看上一篇:Class类文件结构浅析 上一篇讲的都是理论.以下我们亲自实践一下. 首先编写一个简单的java类: public cla ...
- [3 Jun 2015 ~ 9 Jun 2015] Deep Learning in arxiv
arXiv is an e-print service in the fields of physics, mathematics, computer science, quantitative bi ...
- 重温.NET下Assembly的加载过程 ASP.NET Core Web API下事件驱动型架构的实现(三):基于RabbitMQ的事件总线
重温.NET下Assembly的加载过程 最近在工作中牵涉到了.NET下的一个古老的问题:Assembly的加载过程.虽然网上有很多文章介绍这部分内容,很多文章也是很久以前就已经出现了,但阅读之后 ...
- mqtt client python example
This is a simple example showing how to use the [Paho MQTT Python client](https://eclipse.org/paho/c ...
- liunx安装pip
安装pip之前要先安装Anaconda. 1.下载: # wget "https://pypi.python.org/packages/source/p/pip/pip-1.5.4.tar. ...
- 【程序猿联盟】官网上线啦!coderunity.com
wx_fmt=jpeg" alt="" style="max-width:100%; height:auto!important"> 内容简单介 ...
- VS2010配置QT5.5.0开发环境
一.官网下载QT和qtvsaddin插件 网址:http://www.qt.io/download-open-source/ 1. 2. 3. 得到下载的安装包,点击安装就能够了 watermark/ ...
- 李洪强iOS开发之带placeHolder的Textview
李洪强iOS开发之带placeHolder的Textview 01 - 创建工过程,定义全局属性,遵守textview的代理协议 02 - 添加一个textview和一个label 03 - 实现 ...
- Java RESTful 框架
[转载] 最好的8个 Java RESTful 框架 - 2015 Top 8 Java RESTful Micro Frameworks – Pros/Cons - 2017 Restlet - f ...