【总结】关于C++虚函数、多态和对象切割

貌似很少有把这几件事连在一起讲的,在网上查了好久,也翻了半天书才弄明白整个过程是怎么回事。

先说现象再说原理:

 1 // vtableTest.cpp : Defines the entry point for the console application.
 2 //
 3
 4 #include "stdafx.h"
 5 #include "iostream"
 6 using namespace std;
 7
 8 class Base
 9 {
10 public:
11     virtual void function1() {cout << "Base f1" << endl;};
12     virtual void function2() {cout << "Base f2" << endl;};
13 };
14
15 class D1: public Base
16 {
17 public:
18     virtual void function1() {cout << "D1 f1" << endl;};
19 };
20
21 int _tmain(int argc, _TCHAR* argv[])
22 {
23     Base* pb0 = new Base();
24     pb0->function1();
25
26     Base* pb = new D1();
27     pb->function1();
28     pb->function2();
29     pb->Base::function1();
30
31     D1 d1;
32     Base b = d1;
33     b.function1();
34     b.function2();
35     getchar();
36     delete pb0;
37     delete pb;
38     return 0;
39 }

这个输出是:

Base f1
D1 f1
Base f2
Base f1
Base f1
Base f2

虚函数:

当Class中定义了virtual function之后,编译器会给这个Class加入一个虚指针(vptr),并生成一张虚函数表(vtable)。

 1 class Base
 2 {
 3 public:
 4     virtual void function1() {cout << "Base f1" << endl;};
 5     virtual void function2() {cout << "Base f2" << endl;};
 6 };
 7
 8 class D1: public Base
 9 {
10 public:
11     virtual void function1() {cout << "D1 f1" << endl;};
12 };

示例中的Base有一个自己的虚函数表,表里面有function1和function2两个函数。当调用的时候

    Base* pb = new Base();
    pb->function1();

此时会根据pb找到这个对象在内存中的位置,然后找到该对象由编译器生成的vptr,根据vptr找到vtable,然后执行里面的function1方法。

此时的输出是"Base f1"。

多态:

而当这么玩的时候:

    Base* pb = new D1();
    pb->function1();

由于D1继承了Base,也会继承Base的vptr,但会单独生成一张属于D1自己的vtable。vptr会指向这张新的D1的vtable。

D1的定义中重写了function1方法,也就是说,在D1的vtable中,function1被新的方法(定义在D1里的那个)覆盖,而function2与Base中的相同。

于是在调用function1()时,会根据pb找到这个对象在内存中的位置,然后找到该对象由编译器生成的vptr(这个vptr是指向D1的vtable的),在执行vtable中的function1(),这个function1()就是D1中定义的那个了。

于是此时输出是"D1 f1"。

当然了如果非要执行父类的虚函数也是可以的,毕竟Base的vtable还在那,直接调用也行。

pb->Base::function1();

加上Base::会指明(虚?)函数表,让系统直接去找相应的函数来执行。

这样的输出还是"Base f1"。

对象切割:

这个一点都不好玩。

    D1 d1;
    Base b = d1;
    b.function1();

d1占据的内存里由他的父类Base和D1自己独有的内容这两部分构成。当执行Base b = d1; 的时候会发生对象切割(Object Slicing),此时的b仅仅拥有Base类中的成员,D1里的成员就全部被丢掉了。

不过b的成员应该是从d1那里复制过来的,但是为毛function1的时候会输出"Base f1"啊这个问题困扰了我好久。

Base b = d1;这个过程稍微复杂一些。

由于Base中有虚函数,所以是不能“按位拷贝(Bitwise Copy)”的。编译器会给Base单独生成一个赋值函数,并重置vptr把他指向Base自己的vtable。

这么做估计是为了防止虚函数中调用了子类的成员,这样父类被切割后再去调用子类的方法程序应该会挂掉吧。

所以这个例子中还是会输出"Base f1"。

参考:

1,《深入探索C++对象模型》

2,http://www.learncpp.com/cpp-tutorial/125-the-virtual-table/   (这里面讲的更详细)

时间: 2024-11-03 03:44:41

【总结】关于C++虚函数、多态和对象切割的相关文章

虚函数多态的实现细节

之前老是被问到虚函数多态的事情.......有个模棱两可的印象,正好遇到这个帖子了,所以再学习学习 http://www.cnblogs.com/shouce/p/5453729.html 1.什么是虚函数 简单地说:那些被virtual关键字修饰的成员函数就是虚函数.其主要作用就是实现多态性. 多态性是面向对象的核心:它的主要的思想就是可以采用多种形式的能力,通过一个用户名字或者用户接口完成不同的实现.通常多态性被简单的描述为“一个接口,多个实现”.在C++里面具体的表现为通过基类指针访问派生

揭秘虚函数多态的实现细节

1.什么是虚函数 简单地说:那些被virtual关键字修饰的成员函数就是虚函数.其主要作用就是实现多态性. 多态性是面向对象的核心:它的主要的思想就是可以采用多种形式的能力,通过一个用户名字或者用户接口完成不同的实现.通常多态性被简单的描述为“一个接口,多个实现”.在C++里面具体的表现为通过基类指针访问派生类的函数和方法.看下面这段简单的代码: 1 class A 2 { 3 public: 4 void print(){cout << "this is A" <&

4.虚函数-多态

1.多态 多态的条件: (1):继承 (2):父类中有虚函数 (3):在子类中重新实现父类的虚函数(覆盖虚表) (4):把子类对象/指针赋值给父类的引用/指针 (5):通过父类的引用/指针来调用虚函数(只能调用父类中存在的函数) 用C++类以及多态来封装pthread进程 class CppThread{ public: CppThread(){} ~CppThread(){} void start(); virtual void run(){} protected: pthread_t id;

GeekBand-secondweek-c++的多态和虚函数

多态与虚函数 13章的简单继承只是实现了对已有对象的实现的重定义和直接调用,但是向上映射导致的对象切割仍然是个缺陷: 1.延续13章的向上映射 简单继承中,派生类重定义了基类的成员函数,此时,向上映射的结果是很明显的,它使用了基类实现的函数版本,这显然并不是我们想要的效果:为什么会有这样的结果发生,我们先探讨几个问题: 函数调用绑定:函数调用确定目标函数体称为捆绑,编译期绑定称为早绑定,上面的问题就是早绑定引起的,因为编译器只知道基类对象的类型,调用函数也会绑定基类实现的函数版本,要解决这一问题

C++中的多态与虚函数的内部实现

1.什么是多态 多态性可以简单概括为“一个接口,多种方法”. 也就是说,向不同的对象发送同一个消息, 不同的对象在接收时会产生不同的行为(即方法).也就是说,每个对象可以用自己的方式去响应共同的消息.所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数.这是一种泛型技术,即用相同的代码实现不同的动作.这体现了面向对象编程的优越性. 多态分为两种: (1)编译时多态:主要通过函数的重载和运算符的重载来实现. (2)运行时多态:主要通过虚函数来实现. 2.几个相关概念 (1)覆盖.重

C++ Primer 学习笔记33_面向对象编程(4)--虚函数与多态(一):多态、派生类重定义、虚函数的访问、 . 和-&gt;的区别、虚析构函数、object slicing与虚函数

C++ Primer学习笔记33_面向对象编程(4)--虚函数与多态(一):多态.派生类重定义.虚函数的访问. . 和->的区别.虚析构函数.object slicing与虚函数 一.多态 多态可以简单地概括为"一个接口,多种方法",前面讲过的重载就是一种简单的多态,一个函数名(调用接口)对应着几个不同的函数原型(方法). 更通俗的说,多态行是指同一个操作作用于不同的对象就会产生不同的响应.或者说,多态性是指发出同样的消息被不同类型的对象接收时有可能导致完全不同的行为. 多态行分

《C++编程思想》 第十四章 多态和虚函数 (原书代码+习题+讲解)

一.相关知识点 函数调用捆绑 把函数体与函数调用相联系称为捆绑(binding).当捆绑在程序运行之前(由编译器和连接器)完成时,称为早捆绑.我们可能没有听到过这个术语,因为在过程语言中是不会有的:C编译只有一种函数调用,就是早捆绑.上面程序中的问题是早捆绑引起的,因为编译器在只有 instrument地址时它不知道正确的调用函数.解决方法被称为晚捆绑,这意味着捆绑在运行时发生,基于对象的类型.晚捆绑又称为动态捆绑或运行时捆绑.当一个语言实现晚捆绑时,必须有一种机制在运行时确定对象的类型和合适的

【继承与多态】C++:继承中的赋值兼容规则,子类的成员函数,虚函数(重写),多态

实现基类(父类)以及派生类(子类),验证继承与转换--赋值兼容规则: 子类对象可以赋值给父类对象(切割/切片) 父类对象不能赋值给子类对象 父类的指针/引用可以指向子类对象 子类的指针/引用不能指向父类对象(可以通过强制类型转换完成) #include<iostream> using namespace std; class People    //父类或者基类 { public:     void Display()     {         cout << "_na

虚函数 继承 多态

单继承与Data Members 在C++的继承模型中,base class members和derived class members的排列顺序并为强制规定.不同的编译器可能有不同的布局安排.大部分情况下,base class members会安排在derived class members的前面,但base class是virtual base class(base class存在virtual function)除外. 只有继承没有多态 考虑如下程序: class Point2d { pu