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

  

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

原文地址:https://www.cnblogs.com/cthon/p/9250239.html

时间: 2024-11-05 14:33:54

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

C++ 虚函数和虚继承浅析

本文针对C++里的虚函数,虚继承表现和原理进行一些简单分析,有希望对大家学习C++有所帮助.下面都是以VC2008编译器对这两种机制内部实现为例. 虚函数 以下是百度百科对于虚函数的解释: 定义:在某基类中声明为 virtual 并在一个或多个派生类中被重新定 义的成员函数[1] 语法:virtual 函数返回类型 函数名(参数表) { 函数体 } 用途:实现多态性,通过指向派生类的基类指针,访问派生类中同名覆盖成员函数 函数声明和定义和普通的类成员函数一样,只是在返回值之前加入了关键字"vir

虚函数和虚拟继承的内存分布

一.虚函数 (1)C++中的虚函数的主要作用:实现了多态的机制. (2)多态:用父类型的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数.这种技术可以让父类的指针有"多种形态",这是一种泛型技术.所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法.比如:模板技术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议. (3)多态要基于函数重载,所以如果子类没有重载父类的虚函数那是一件毫无意义的事情. 二.虚函数表 1.虚函数表:虚函数表C+

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

C++面向对象中,虚函数与虚继承是两个完全不同的概念. 一.虚函数 C++程序中只要类中含有虚拟函数,编译程序都会为此类生成一个对应的虚拟函数跳转表(vtbl),该虚拟函数跳转表是一个又若干个虚拟函数体入口地址组成的一个线性表.派生类的虚拟函数跳转表的前半部分由父类的vtbl得出,但是里面的内容不一定相同,后半部分则对应着自己新定义的虚拟函数. class Employee { protected: char *Name; int Age; public: void changeAge(int

C++继承详解之三——菱形继承+虚继承内存对象模型详解vbptr(1)

在我个人学习继承的过程中,在网上查阅了许多资料,这些资料中有关菱形继承的知识都是加了虚函数的,也就是涉及了多态的问题,而我在那个时候并没有学习到多态这一块,所以看很多资料都是云里雾里的,那么这篇文章我想以我自己学习过程中的经验,由简到较难的先分析以下菱形继承,让初学者先对这个问题有一点概念,在后面会由浅入深的继续剖析. 本篇文章不会涉及到多态也就是虚函数的菱形继承,在后面的文章更新中,我会慢慢把这些内容都加进去. 菱形继承(也叫钻石继承)是下面的这种情况: 对应代码如下: #include <i

C++纯虚函数、虚函数、实函数、抽象类,重载、重写、重定义

首先,面向对象程序设计(object-oriented programming)的核心思想是数据抽象.继承.动态绑定.通过数据抽象,可以使类的接口与实现分离,使用继承,可以更容易地定义与其他类相似但不完全相同的新类,使用动态绑定,可以在一定程度上忽略相似类的区别,而以统一的方式使用它们的对象. 虚函数的作用是实现多态性(Polymorphism),多态性是将接口与实现进行分离,采用共同的方法,但因个体差异而采用不同的策略.纯虚函数则是一种特殊的虚函数.虚函数联系到多态,多态联系到继承. 一.虚函

C++虚函数及虚函数表解析

一.背景知识(一些基本概念) 虚函数(Virtual Function):在基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数.纯虚函数(Pure Virtual Function):基类中没有实现体的虚函数称为纯虚函数(有纯虚函数的基类称为虚基类).C++  “虚函数”的存在是为了实现面向对象中的“多态”,即父类类别的指针(或者引用)指向其子类的实例,然后通过父类的指针(或者引用)调用实际子类的成员函数.通过动态赋值,实现调用不同的子类的成员函数(动态绑定).正是因为这种

C++虚函数与虚析构函数

在类中,有两个与众不同的成员函数,那就是构造函数和析构函数.当构造函数与析构函数遭遇继承和多态,它们的运行状况又会出现什么变化呢? 多态性是在父类或各子类中执行最合适成员函数.一般来说,只会选择父类或子类中的某一个成员函数来执行.这可给析构函数带来了麻烦!如果有的资源是父类的构造函数申请的,有的资源是子类的构造函数申请的,而虚函数只允许程序执行父类或子类中的某一个析构函数,岂不是注定有一部分资源将无法被释放?为了解决这个问题,虚析构函数变得与众不同. 下面我们就来给析构函数的前面加上保留字vir

C++ 虚函数&amp;纯虚函数&amp;抽象类&amp;接口&amp;虚基类(转)

http://www.cnblogs.com/fly1988happy/archive/2012/09/25/2701237.html 1. 多态 在面向对象语言中,接口的多种不同实现方式即为多态.多态是指,用父类的指针指向子类的实例(对象),然后通过父类的指针调用实际子类的成员函数. 多态性就是允许将子类类型的指针赋值给父类类型的指针,多态是通过虚函数实现的. 多态可以让父类的指针有“多种形态”,这是一种泛型技术.(所谓泛型技术,就是试图使用不变的代码来实现可变的算法). 2. 虚函数 2.1

虚函数与虚析构函数原理

----------------siwuxie095 关于虚函数和虚析构函数的实现原理,因为涉及到 函数指针, 所以先介绍什么是函数指针 函数指针 如果通过一个指针指向对象,就称其为 对象指针,指针除了可以 指向对象之外,也可以指向函数,就称其为 函数指针 函数的本质,其实就是一段二进制的代码,它写在内存中, 可以通过指针来指向这段代码的开头,计算机就会从开头 一直往下执行,直到函数的结尾,并通过指令返回回来 如果有这么 5 个函数指针,它们所存储的就是 5 个函数的函数地址,当 使用时,如:使