准备工作

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. hibernate session缓存

    Session 概述 Session 接口是 Hibernate 向应用程序提供的操纵数据库的最基本的接口, 它提供了基本的保存, 更新, 删除和载入 Java 对象的方法. Session 具有一个 ...

  2. 使用 xmllint 验证 odoo xml文件

    Odoo 源码包含了2个 relax ng 文件,也是odoo sa用来验证xml的正确性的. openerp/import_xml.rng openerp/addons/base/rng/view. ...

  3. C++零基础到入门

    (1)C语言概述 (2)编写.运行一个简单的C语言程序 (3)数据类型 (4)运算符和表达式 如果你对C语言一窍不通,那你就好好看这篇文章,我会力争让你真正的做到从零基础到入门,同时这篇文章会让你基本 ...

  4. Thunderbolt雷电接口

    官网:https://thunderbolttechnology.net/tech/certification

  5. How to reset your password in Ubuntu

    There are many reasons you might want to reset a password: Someone gave you a computer with Ubuntu i ...

  6. kubernetes 之QoS服务质量管理

    系列目录 在kubernetes中,每个POD都有个QoS标记,通过这个Qos标记来对POD进行服务质量管理.QoS的英文全称为"Quality of Service",中文名为& ...

  7. Day20 Java Socket使用

    Java中Socket的使用 client端 package org.tizen.test; import java.io.IOException; import java.io.OutputStre ...

  8. codevs1032

    题目地址:http://codevs.cn/problem/1032/ 分析: 题目数据有错.这题过不了才正常. 我调了非常久可是就是有两个点过不去.于是我把数据下了下来,找到WA的第五个点和第七个点 ...

  9. jquery动态加载脚本

    如果你使用的是jQuery,它里面有一个内置的方法可以用来加载单个JS文件.当你需要延迟加载一些js插件或其它类型的文件时,可以使用这个方法. 一.jQuery getScript()方法加载java ...

  10. Hadoop - YARN 概述

    一 概述       Apache Hadoop YARN (Yet Another Resource Negotiator,还有一种资源协调者)是一种新的 Hadoop 资源管理器,它是一个通用资源 ...