浅谈为什么只有指针能够完成多态及动态转型的一个误区

c++多态由一个函数地址数组Vtable和一个指向Vtable的指针vptr实现。

具体来说,类拥有自己的vtable,类的vtable在编译时刻完成。

每个对象有自己的vptr指针,该指针初始化时指向对象所实现的类的vtable。

关于向上转型的误区:

  通常对于向上转型的理解是这样的,当子类对象向上转型(允许隐式)成父类对象时,实际上只是将子类对象暂时看做父类对象,内部的数据并未改变。

  对于没有虚函数的对象,这句话是正确的,但是,当引入虚函数后,这样的理解是有问题的,实际上,向上转型的过程中,vptr指针的指向随着转型改变,这一过程是upcast机制定义好的,也就是说,在任何时刻需要将子类看成父类时,vptr就动态的改变指向,Vprt指针总是指向对象的动态类型。

推广之,任何的类型转化都导致vptr指向的改变,指向所转型的类的vtable,如果对应类没有vtable,则动态删除vptr。

下面解释为什么只有指针和引用才具有多态性,测试如下:

#include <iostream>
using namespace std;

class A{
    virtual void func(){
        cout<<"A‘s func is called"<<endl;
    }
};

class B:public A{
    virtual void func(){
        cout<<"B‘s func is called"<<endl;
    }
};

int main(){
    A a;
    B b;
    cout<<"Vtable address of A is"<<*(void**)&a<<endl;
    cout<<"Vtable address of B is"<<*(void**)&b<<endl;
    A tmp=b;
    cout<<"after upcast and copy:  "<<*(void**)&tmp<<endl;
    A* p=&b;
    cout<<"after upcast and reference:  "<<*(void**)&(*p)<<endl;
}

测试结果如下:

Vtable address of A is0x1000020e0

Vtable address of B is0x100002110

after upcast and copy:  0x1000020e0

after upcast and reference:  0x100002110

可见,确实只有指针才实现了多态。

原因如下:

A tmp=b;

当向父类引用拷贝一个子类对象时,这时实际发生的是,子类对象先向上转型成父类对象,然后在进行赋值运算,由于未定义=的运算符重载,所以发生的一定是bitwise copy,因此,tmp和b的vptr指向不一致,一定是b在发生隐式的向上转型时发生了改变。

A* p=&b;

同样,该过程包含一个向上转型和一个拷贝赋值,不同的是,向上转型和拷贝都是地址,首先,b的地址(B*)向上转型成(A*)类,然后将该地址的副本交给p,与值拷贝不同的是,A*和B*根本就没有vptr,也就不存在vptr指向改变的问题了。

通过这种方式,我们使用一个父类的引用,但vptr并未改变,依旧指向了对象的动态类型,多态也得以实现。

ps,在函数传参过程中的多态也是一样的。

对于这样一个函数

void fun(A a);  将B b传入时,首先发生了b的向上转型,然后发生了值拷贝,vptr发生了改变,多态失败。

void fun(A* a)  将B* b传入时,发生b的向上转型和拷贝,但是地址变量的转型和拷贝不改变vptr的指向,vptr指向得以保留,多态成功。

浅谈为什么只有指针能够完成多态及动态转型的一个误区,布布扣,bubuko.com

时间: 08-17

浅谈为什么只有指针能够完成多态及动态转型的一个误区的相关文章

浅谈RAII&智能指针

关于RAII,官方给出的解释是这样的"资源获取就是初始化".听起来貌似不是很懂的哈,其实说的通俗点的话就是它是一种管理资源,避免内存泄漏的一种方法.它可以保证在各种情况下,当你对对象进行使用时先通过构造函数来进行资源的分配和初始化,最后通过析构函数来进行清理,有效的保证了资源的正确分配和释放.(特别是在异常中,因为异常往往会改变代码正确的执行顺序,这就很容易引起资源管理的混乱和内存的泄漏) 其中智能指针就是RAII的一种实现模式,所谓的智能就是它可以自动化的来管理它所指向那份空间的资源

转 浅谈C++中指针和引用的区别

浅谈C++中指针和引用的区别 浅谈C++中指针和引用的区别 指针和引用在C++中很常用,但是对于它们之间的区别很多初学者都不是太熟悉,下面来谈谈他们2者之间的区别和用法. 1.指针和引用的定义和性质区别: (1)指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元:而引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已.如: int a=1;int *p=&a; int a=1;int &b=a; 上面定义了一个整形变量和一个指针变量p,该指针变量指向a

C++ 浅谈C++中指针和引用

浅谈C++中指针和引用的区别 指针和引用在C++中很常用,但是对于它们之间的区别很多初学者都不是太熟悉,下面来谈谈他们2者之间的区别和用法. 1.指针和引用的定义和性质区别: (1)指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元:而引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已.如: int a=1;int *p=&a; int a=1;int &b=a; 上面定义了一个整形变量和一个指针变量p,该指针变量指向a的存储单元,即p的值是a存储单

浅谈C++中指针和引用的区别

指针和引用在C++中很常用,但是对于它们之间的区别很多初学者都不是太熟悉,下面来谈谈他们2者之间的区别和用法. 1.指针和引用的定义和性质区别: (1)指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元:而引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已.如: int a=1;int *p=&a; int a=1;int &b=a; 上面定义了一个整形变量和一个指针变量p,该指针变量指向a的存储单元,即p的值是a存储单元的地址. 而下面2句定义了一

浅谈数组与指针

对于一维数组与指针: 对于二维数组与指针:

浅谈java中内置的观察者模式与动态代理的实现

一.关于观察者模式 1.将观察者与被观察者分离开来,当被观察者发生变化时,将通知所有观察者,观察者会根据这些变化做出对应的处理. 2.jdk里已经提供对应的Observer接口(观察者接口)与Observable(被观察者类)用于实现观察者模式 3.关于Observer接口,该接口只有一个update方法,当被观察者发生相关变化时,会通知所有的观察者,观察者接受到通知时,调用update方法进行处理.贴出源代码: 1 /* 2 * Copyright (c) 1994, 1998, Oracle

浅谈C# 多态的魅力(虚方法,抽象,接口实现)

浅谈C# 多态的魅力(虚方法,抽象,接口实现) 分类:             asp.net             C#              2014-02-08 11:29     786人阅读     评论(0)     收藏     举报 浅谈C# 多态的魅力(虚方法,抽象,接口实现) 前言:我们都知道面向对象的三大特性:封装,继承,多态.封装和继承对于初学者而言比较好理解,但要理解多态,尤其是深入理解,初学者往往存在有很多困惑,为什么这样就可以?有时候感觉很不可思议,由此,面向

转: 浅谈C/C++中的指针和数组(二)

转自:http://www.cnblogs.com/dolphin0520/archive/2011/11/09/2242419.html 浅谈C/C++中的指针和数组(二) 前面已经讨论了指针和数组的一些区别,然而在某些情况下,指针和数组是等同的,下面讨论一下什么时候指针和数组是相同的. C语言标准对此作了说明: 规则1:表达式中的数组名被编译器当做一个指向该数组第一个元素的指针: 注:下面几种情况例外 1)数组名作为sizeof的操作数 2)使用&取数组的地址 规则2:下标总是与指针的偏移量

转:浅谈C/C++中的指针和数组(一)

转自:http://www.cnblogs.com/dolphin0520/archive/2011/11/09/2242138.html 浅谈C/C++中的指针和数组(一) 指针是C/C++的精华,而指针和数组又是一对欢喜冤家,很多时候我们并不能很好的区分指针和数组,对于刚毕业的计算机系的本科生很少有人能够熟练掌握指针以及数组的用法和区别.造成这种原因可能跟现在大学教学以及现在市面上流行的很多C或者C++教程有关,这些教程虽然通俗易懂,但是在很多关键性的地方却避而不谈或者根本阐述不清楚,甚至很