为什么要使用虚函数和 指针(或是引用)才能实现多态?

补充:(2016.04.05)

上次使用了g++ -fdump-tree-original test3.cpp 命令看到了编译器补充后的函数是什么样子的,但是没有记录,今天补充下:

   下文中第一main函数的代码使用如上命名编译后生成一个名为:test3.cpp.003t.original的文件。打开文件查看后会发下编译器给补充的default构造函数和copy函数分别如下图所示:




       从图中可以看到,编译器会自动给代码添加默认的构造函数和copy构造函数,同时在此两个构造函数中加入,对 _vptr.shape(指向虚表的,虚指针)赋值,指向Shape类的虚函数表,之所以附带贴上编译器添加的默认构造函数,是为了说明copy构造函数中的虚指针确实是指向Shape类的虚函数表。

       在咱们的例子中,当以shape1,shape2作为OutputShape()函数的参数时,由于OutputShape函数定义时的默认参数为Shape类型,而传进去的参数均为Shape类型的子类,此时会隐式调用Shape类的copy构造函数。而copy构造函数是只对成员变量进行拷贝, 虚指针(_vpyr)指向Shape的虚函数表。所以,输出结构就是……。

       对下文的第二个main函数的代码使用如上命名编译后生成一个名为:test3.cpp.003t.original的文件。打开文件查看后会发下编译器给补充的default构造函数和copy函数之外,还给默认补充了copy assignment operator【注意,上边的代码中并没有生成copy assignment operator函数,《深入理解C++对象模型》中说,编译器默认会给代码生成4个函数,分别是:default构造函数(前提是类没有声明任何构造函数)、copy构造函数、copy assignment operator和析构函数;但同时也说,如果四种函数在函数中根本就没有使用的时候,则不会生成之,有用的叫non-trivial的,没用的叫trivial的;此次生成copy assignment operator函数,而上边的情况编译器并没有生成此copy assignment operator函数也印证了此说法】,下图所示为编译器生成的copy assignment operator函数:


观察发现此copy assignment operator函数的形式参数为Shape的const的引用,所以此问题其实是对上边描述的情况的一个包装!

        对下文的第三个main函数的代码使用如上命名编译后生成一个名为:test3.cpp.003t.original的文件。

    此部分需要自己对照第3个main函数仔细分析,其实我现在还是没有完全明白!因为我手工更改了_vptr指针的值,所以以指针的方式的temp1->DrawSelf()调用DrawSelf函数时,编译器将其翻译为:

OBJ_TYPE_REF(*NON_LVALUE_EXPR <NON_LVALUE_EXPR <temp1>->_vptr.Shape>;NON_LVALUE_EXPR <temp1>->0) (NON_LVALUE_EXPR <temp1>) >>

所以执行后输出结果为“连接各顶点”;但是以temp.DrawSelf()的方式调用DrawSelf函数时,编译器将其翻译为:DrawSelf (&temp) >>>

但是我查找了整个文件,并没有找到参数为Shape*的DrawSelf函数,我猜测此处是因为DrawSelf被声明为虚函数,此处是直接以Shape::DrawSelf的方式调用了Shape的虚函数???此处仍是一个疑问,倘若有高手看到,还望指教,感谢!

至此,此文此文完结,剩余的一个疑问后续学习时留心解决!(2016.04.05晚20:57)

补充:

http://blog.csdn.net/zoopang/article/details/14071779此文中一楼的朋友的回复:——"使用对象时,成员函数的调用派遣是由编译器确定后硬编码进程序的,没有经过虚函数表。"符合了我上文的猜测,但是原理还是不太明了!(2017.2.10-16:43)

补充:

今天(2016.3.16)使用如下命令在查看编译器是如何忘构造函数里加代码的时候突然有了个疑问:虚函数表是属于类的还是对象的,因为我看到貌似是直接赋一个固定的值给_vptr。网上搜索了下,确定了自己的猜测:虚函数表是属于类的!

g++ -fdump-tree-original test3.cpp 

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

正文:

        首先说说为什么是这样一个题目,其实我最开始思考的是子类实例化的对象赋值给父类实例化的对象,为什么不能实现多态!搜索后发现一个哥们儿跟我的疑问一样,但是题目是这个,我也觉的这个题目更好些。

        最近我在学习《深度探索C++对象模型》这本书,明白了C++对象模型的内存布局。但也恰巧是这个内存布局让我又一次陷入了深深的疑惑之中。先看看我的例子:

注:此例也是引用某位博主的,只是搜索的内容太多了,找不到原连接的位置了……。

#include <iostream>
using namespace std;

class Polygon;
class Shape//形状
{
public:
    virtual void DrawSelf()//绘制自己
    {
       cout << "我是一个什么也绘不出的图形" << endl;
    }
};

class Polygon:public Shape//多边形
{
public:
    void DrawSelf()   //绘制自己
    {
       cout << "连接各顶点" << endl;
    }
};

class Circ:public Shape//圆
{
public:
    void DrawSelf()   //绘制自己
    {
       cout << "以圆心和半径为依据画弧" << endl;
    }
};

void OutputShape(Shape arg)//专门负责调用形状的绘制自己的函数
{
    arg.DrawSelf();
}

int main()
{
    Polygon shape1;
    Circ shape2;
    //Shape temp;
    //temp=shape1;
    //temp.must(shape1);
    OutputShape(shape1);
    OutputShape(shape2);
}
上述代码执行后输出的结果为:

        为什么是这样的结果?因为在调用OutputShape(shape1) 和 OutputShape(shape2)时,在参数传递时会调用arg(Shape)的copy构造函数,arg的copy构造函数中应该仅仅是对成员变量的拷贝!(已经确认仅仅只拷贝成员变量的值,不会涉及_vptr的赋值),所以_vptr还是指向原来虚函数表,所以调用的仍为基类的DrawSelf()函数。(注意此处将会产生slice问题,即将子类实例内存布局中从父类继承来的变量copy 到父类实例的对应成员变量中,子类自身的成员变量被舍弃。)

注:此处我需要搞清楚编译器自动给加的copy构造函数究竟长什么样?但是我现在还没找到好的办法。(已解决,参见2016.04.05的补充

如果将上述的main函数改为:

int main()
{
    Polygo shape1;
    Circ shape2;
    Shape temp;
    temp=shape1;
    //temp.must(shape1);
    OutputShape(temp);
    OutputShape(shape2);
}

输出的结果仍然为:


为什么是这样的结果?arg的copy assignment operator中应该仅仅是对成员变量的拷贝!所以_vptr还是指向原来虚函数表,所以调用的仍为基类的DrawSelf()函数。

注:此处我需要搞清楚编译器自动给加的 copy assignment operator函数究竟长什么样?但是我现在还没找到好的办法。(已解决,参见2016.04.05的补充

基于以上两个疑问,我在搜索的过程中又发现了篇关于深拷贝和浅拷贝的文章,觉的写的浅显易懂,也转载了过来。

对于彻底搞清楚copy构造函数和copy assignment operator也需要一篇文章(这篇文章中和effective C++ 中的条款5有不一致的地方需要验证,究竟谁说的对)。


        起初我碰到这个问题时上网搜索了下,发现了这篇文章中作者和我其实有相同的疑问。通过阅读作者的文章,了解到了是因为_vptr没有拷贝的原因,但是作者的疑问也同样困扰了我,到现在也没有想到合理的解释。我的实验待如下(基于上述代码):

#include <iostream>
using namespace std;

class Polygon;
class Shape//形状
{
public:
    virtual void DrawSelf()//绘制自己
    {
       cout << "我是一个什么也绘不出的图形" << endl;
    }

    Shape& must (const Polygon& b)
    {
        cout<<"i am in"<<endl;
        *(long *)this=*(long *)&b;
        this->DrawSelf();
        return *this;
    }
};

class Polygon:public Shape//多边形
{
public:
    void DrawSelf()   //绘制自己
    {
       cout << "连接各顶点" << endl;
    }
};

class Circ:public Shape//圆
{
public:
    void DrawSelf()   //绘制自己
    {
       cout << "以圆心和半径为依据画弧" << endl;
    }
};

void OutputShape(Shape arg)//专门负责调用形状的绘制自己的函数
{
    arg.DrawSelf();
}

int main()
{
    Polygon shape1;
    Circ shape2;
    Shape temp;
    temp.must(shape1);//i am in   and  连接各个节点
    Shape *temp1=&temp;
    temp1->DrawSelf();//连接各个节点
    temp.DrawSelf();//我是一个什么也绘不出的图形
    OutputShape(shape1);//我是一个什么也绘不出的图形
    OutputShape(shape2);//我是一个什么也绘不出的图形
}
此段函数的输出结果是:



       我在Shape类中加入一个must()函数来强行改变此Shape实例的_vptr的值为一个Polygo实例的_vptr的值,并直接在must()函数中调用DrawSelf()函数,输出达到预期。在main()函数中使用指针的方式来调用DrawSelf()也符合预期。但是使用temp.DrawSelf()的方式调用时,结果却出乎意料的输出了“我是一个什么也绘不出的图形”,但是预期确实是 “连接各顶点”。这肯定是编译器解释  temp.DrawSelf()  的方式导致的,但是我现在还没有办法解释到底怎么回事儿!


注:此处我需要搞清楚编译器究竟是怎么解释    temp.DrawSelf() 的?但是我现在还没找到好的办法。(已解决,参见2016.04.05的补充



记录下自己的思考过程,请忽略:

为什么是这样的结果?按照之前此文章的实践验证和《深度理解C++对象模型》的理解,shape1和shape2的内存布局以及Shape类的实例的内存布局,如上图所示:

那么将shape1和shape2赋给arg,其内存布局中父类部分的值就都拷贝过去了,由于只有一个_vptr指针,所以此指针就覆盖了arg中原有的_vptr中的值了呀,那么调用虚函数查询虚表时,就应该和使用指针(引用)的效果一样呀,都应该调用的是子类中的DrawSelf()函数呀,怎么最后调用了基类的DrawSelf()函数了呢?too naive o(∩_∩)o 哈哈。


图中“其他成员”代表可以声明别的成员变量,代码中为了简单并未定义成员变量。(这样看对颈椎好,对不住了。也简单,看一眼就好了)


  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值