分享

C++ 探索(virtual 之二)

 astrotycoon 2015-09-01
    探索C++多态性基石:virtual 特性

C++的效率一定比C要低吗?不能这么说!
只能说:确实某些时候,C++的效率比C要稍低。

C++在以下三种情况下,会产生额外的负担(空间和效率)

1:引入了virtual function之后。
2:引入了virtual base class之后。
3:多重继承下个别情况。

在别的情况下,我并没有感觉到C++比C效率要低。


在一个单一继承下(非virtual function,非virutal base class)的代码,如下:

class point2d {
public:
        … …
private:
     float _x, _y;
};
class point3d : public point2d {
public:
… …
private:
     float _z;
};
和一个C 的 struct 代码如下:

struct point3d {
      float _x, _y, _z;
}
在空间与效率上并没有什么分别!但C++提供了更好的封装与继承特性 !

在这里,我们来看一看,多重继承下,到底什么影响了空间与效率。

在讲virtual多重继重前,先讲一讲非virutal 的多重继承

一.        非virutal function 非 virtual base class下的多重继承
看下面代码:

#include ;

class point2d {
public:
        point2d(float x = 0.0, float y = 0.0)
                : _x(x), _y(y) {};

        float x() { return _x; };
        float y() { return _y; };
        float z() { return 0.0; };
private:
        float _x, _y;
};

class point3d : public point2d {
public:
        point3d(float x = 0.0, float y = 0.0, float z = 0.0)
                : point2d(x, y), _z(z) {};
       
        float z() { return _z; };

private:
        float _z;
};

class vertex {
public:
        vertex(vertex *p = 0) : next(p) {};
        void foo() { printf("vertex subobject "); };
private:
        vertex *next;
};


class vertex3d : public point3d,
                 public vertex
{
public:
        vertex3d(float x = 0.0, float y = 0.0, float z = 0.0)
                : point3d(x, y, z), vertex(0), mumble(0.0) {};
private:
        float mumble;
};

int main()
{
        vertex3d v3d;
        vertex *pv = &v3d;
        pv->;foo();
       
        return 0;
};
结果是,调用vertex::foo()。
在不含virtual function 与 virtual base class的情形下,多重继承主要的效率负担是:derived class与第二个base class及后继base class(更多的base class)的this指针的调整。上例的:

vertex *pv = &v3d;
为了调用vertex::foo(),编译器必须作出一些调整,将v3d的this指针调整为vertex的this指针,如何调整?
编译器这样做:

    vertex *pv = (vertex *)(this + sizeof(point3d));
经过这样调整后,pv可以正确寻址到foo();

我们来验证一下:
我用gcc3.3.1来编译上述代码,得出如下(main()代码):

movl        $0x0, %eax
movl        %eax, 12(%esp)
movl        $0x0, %eax
movl        %eax, 8(%esp)
movl        $0x0, %eax
movl        %eax, 4(%esp)
leal        -40(%ebp), %eax
movl        %eax, (%esp)                // v3d object this pointer
call        _ZN8vertex3dC1Efff                // call vertex3d’s constructor
leal        -40(%ebp), %eax
addl        $12, %eax                // 注意此处: 作出调整!!!
movl        %eax, -44(%ebp)          // 这是vertex subobject的this
movl        -44(%ebp), %eax
movl        %eax, (%esp)
call        _ZN6vertex3fooEv                // call vertex::foo()
movl        $0, %eax
leave
我解释一下:

(1)        point2d subobject 的size是:8 bytes
(2)        point3d subobject 的size是:point2d subobject size + 4 = 12 bytes
(3)        vertex subobject 的size是:4 bytes
(4)        整个vertex3d object的size 是:12 + 4 = 16 bytes


leal -40(%ebp), %eax
addl $12, %eax

上述代码:
-40(%ebp)处是:v3d的地址,也就是this指针
将v3d this 加上偏移量12(point3d subobject的大小)就得出vertex subobject 的this 指针。

因此,将devived object this 赋给第二个base class subobject指针,必须作出调整才能正确访问到vertex::foo()。


那么。将v3d赋给point2d 指针或是point3d指针就不用调整吗?
答案:是的。
因为,它们的起始地址是相同的,也就是说:point2d 、point3d以及vertex3d的this值是一样的。
多重继承下的object表现在承担着上述的负担。而在加入virtual 机制后
负担会进一步加重。空间也会得到扩充。

下一次,再来研究virtual下的多重继承。

1)在我的陈述中,我有说过上面的代码进行动态绑定吗? 那是,编译期已经确定了的。
     需要调整this指针,这不是额外动作是什么?即使是编译期!
     如果:

           point2d *p2d = new vertex3d;
           p2d->;x();

这样的话,是不需要调整this指针,那么,在这里,就没有额外的调整动作,也就没有额外的负担。难道,在编译时候,编译器安插的动作就不算是负担!
    如果,我在point2d class 中将x (),y(),z()改为virtual function
    那么上述代码:

      point2d *p2d = new vertex3d;
           p2d->;x ();

编译器怎样令p2d调用x()呢? (注意,这是编译期间的调整)

       (*p2d->;vptr[1])(p2d);           // 调用verter3d::x()

上面执行了动态绑定。注意,这是编译期间就调整好了,格式是固定的,这就是额外的负担。
    那么,为什么会调用verter3d:: x (),而不调用point2d:: x ()呢?
    当:

     p2d = new point3d;
         p2d->;x ();

时,调用point3d:: x ()。那么编译的调整呢?(注意:一样的)

     (*p2d->;vptr[1])(p2d);          // 固定的。。。。

看到没有??上面的调整和,将vertex3d object 给p2d赋值是一样的。
    这就是C++ virtual 机制的负担,这些是编译期确定的!!!!!
    秘诀就在于:
    virtual table 的不同。
    我所表述的多重继承关系中,如果将point2d:: x ()、point2d::y()以及point2d::z() 改为virtual function。
    那么, 整个继承体系中,有三个virtual table 。
    1、point2d 的virtual table。
    它的内容是这样的:

*vptr[0]:            type_info  class object
*vptr[1]:            point2d::x()
*vptr[2]:            point2d::y()
*vptr[3]:            point2d::z()

2、point3d 的 virtual table.
     它的内容是这样的:

*vptr[0]:          type_info class object
*vptr[1]:          point2d::x()
*vptr[2]:          point2d::y()      
*vptr[3]:          point3d::z()                // 注意这里:由point3d class 改写了point2d::z()

3、vertex3d 的virtual table
     它的内容是这样的:

*vptr[0]:          type_info class object
*vptr[1]:          point2d::x()
*vptr[2]:          point2d::y()
*vptr[3]:          point3d::z()            // 继承自 point3d class

以上三个virtual table 是编译器产生的,当然都是编译期产生的,当然空间上的负担,与时间上的负担(经过间接提领)。
   而:每一个class subobject 的 vptr 设定是由 constructor 自动设定的。而本例中的 constructor 是由编译器自动扩充为:

point3d::point3d(float x = 0.0, float y = 0.0, float z = 0.0)
           : point2d(x, y), _z(z)
{
              point2d::point2d(x, y);
              _vptr = vtbl_for_point3d;
              _z = z;
}

由上所述:
   无论:

      p2d = new point3d;

还是:

      p2d = new vertex3d;

或者是:

      p2d = new point2d;

它们的调用:

         p2d->;x();

方式都是一样的:
   即:

    (*p2d->;vptr[1])(p2d);      

格式是固定的。是在编译期间设定的,所不同的只是对应的 virtual table不同,也即是vptr不同而已。而 vptr 设定是由 constructor 设定,也是编译期间设定的。而只有调整了this指针才能访问到正确的 subobject !!
   如果, class 中没定义 constructor, 将由编译器合成一个 default constructor 以正确设置 vptr。

   我不明白,你为什么说这不是负担(编译期间设定)???
   编译器经过这样的间接方式寻址到确定是执行体,产生动态效果。
   只是产生动态效果,并不是真正执行时才确定,不经过编译器的调节?那么,你的代码真神了!!

   在非 virtual 的多重继承下,必要时只需调整 this 就行了,不需要调整 vptr
   相信,在这一点上,我已经说得很明白了!!!


    探索C++多态性基石:virtual 特性

C++的效率一定比C要低吗?不能这么说!
只能说:确实某些时候,C++的效率比C要稍低。

C++在以下三种情况下,会产生额外的负担(空间和效率)

1:引入了virtual function之后。
2:引入了virtual base class之后。
3:多重继承下个别情况。

在别的情况下,我并没有感觉到C++比C效率要低。


在一个单一继承下(非virtual function,非virutal base class)的代码,如下:

class point2d {
public:
        … …
private:
     float _x, _y;
};
class point3d : public point2d {
public:
… …
private:
     float _z;
};

和一个C 的 struct 代码如下:

struct point3d {
      float _x, _y, _z;
}

在空间与效率上并没有什么分别!但C++提供了更好的封装与继承特性 !

在这里,我们来看一看,多重继承下,到底什么影响了空间与效率。

在讲virtual多重继重前,先讲一讲非virutal 的多重继承

一.        非virutal function 非 virtual base class下的多重继承
看下面代码:

#include ;

class point2d {
public:
        point2d(float x = 0.0, float y = 0.0)
                : _x(x), _y(y) {};

        float x() { return _x; };
        float y() { return _y; };
        float z() { return 0.0; };
private:
        float _x, _y;
};

class point3d : public point2d {
public:
        point3d(float x = 0.0, float y = 0.0, float z = 0.0)
                : point2d(x, y), _z(z) {};
       
        float z() { return _z; };

private:
        float _z;
};

class vertex {
public:
        vertex(vertex *p = 0) : next(p) {};
        void foo() { printf("vertex subobject "); };
private:
        vertex *next;
};


class vertex3d : public point3d,
                 public vertex
{
public:
        vertex3d(float x = 0.0, float y = 0.0, float z = 0.0)
                : point3d(x, y, z), vertex(0), mumble(0.0) {};
private:
        float mumble;
};

int main()
{
        vertex3d v3d;
        vertex *pv = &v3d;
        pv->;foo();
       
        return 0;
};

结果是,调用vertex::foo()。
在不含virtual function 与 virtual base class的情形下,多重继承主要的效率负担是:derived class与第二个base class及后继base class(更多的base class)的this指针的调整。上例的:

vertex *pv = &v3d;

为了调用vertex::foo(),编译器必须作出一些调整,将v3d的this指针调整为vertex的this指针,如何调整?
编译器这样做:

    vertex *pv = (vertex *)(this + sizeof(point3d));

经过这样调整后,pv可以正确寻址到foo();

我们来验证一下:
我用gcc3.3.1来编译上述代码,得出如下(main()代码):

movl        $0x0, %eax
movl        %eax, 12(%esp)
movl        $0x0, %eax
movl        %eax, 8(%esp)
movl        $0x0, %eax
movl        %eax, 4(%esp)
leal        -40(%ebp), %eax
movl        %eax, (%esp)                // v3d object this pointer
call        _ZN8vertex3dC1Efff                // call vertex3d’s constructor
leal        -40(%ebp), %eax
addl        $12, %eax                // 注意此处: 作出调整!!!
movl        %eax, -44(%ebp)          // 这是vertex subobject的this
movl        -44(%ebp), %eax
movl        %eax, (%esp)
call        _ZN6vertex3fooEv                // call vertex::foo()
movl        $0, %eax
leave

我解释一下:

(1)        point2d subobject 的size是:8 bytes
(2)        point3d subobject 的size是:point2d subobject size + 4 = 12 bytes
(3)        vertex subobject 的size是:4 bytes
(4)        整个vertex3d object的size 是:12 + 4 = 16 bytes



leal -40(%ebp), %eax
addl $12, %eax

上述代码:
-40(%ebp)处是:v3d的地址,也就是this指针
将v3d this 加上偏移量12(point3d subobject的大小)就得出vertex subobject 的this 指针。

因此,将devived object this 赋给第二个base class subobject指针,必须作出调整才能正确访问到vertex::foo()。


那么。将v3d赋给point2d 指针或是point3d指针就不用调整吗?
答案:是的。
因为,它们的起始地址是相同的,也就是说:point2d 、point3d以及vertex3d的this值是一样的。
多重继承下的object表现在承担着上述的负担。而在加入virtual 机制后
负担会进一步加重。空间也会得到扩充。

下一次,再来研究virtual下的多重继承。

1)在我的陈述中,我有说过上面的代码进行动态绑定吗? 那是,编译期已经确定了的。
     需要调整this指针,这不是额外动作是什么?即使是编译期!
     如果:

           point2d *p2d = new vertex3d;
           p2d->;x();

这样的话,是不需要调整this指针,那么,在这里,就没有额外的调整动作,也就没有额外的负担。难道,在编译时候,编译器安插的动作就不算是负担!
    如果,我在point2d class 中将x (),y(),z()改为virtual function
    那么上述代码:

      point2d *p2d = new vertex3d;
           p2d->;x ();

编译器怎样令p2d调用x()呢? (注意,这是编译期间的调整)

       (*p2d->;vptr[1])(p2d);           // 调用verter3d::x()

上面执行了动态绑定。注意,这是编译期间就调整好了,格式是固定的,这就是额外的负担。
    那么,为什么会调用verter3d:: x (),而不调用point2d:: x ()呢?
    当:

     p2d = new point3d;
         p2d->;x ();

时,调用point3d:: x ()。那么编译的调整呢?(注意:一样的)

     (*p2d->;vptr[1])(p2d);          // 固定的。。。。

看到没有??上面的调整和,将vertex3d object 给p2d赋值是一样的。
    这就是C++ virtual 机制的负担,这些是编译期确定的!!!!!
    秘诀就在于:
    virtual table 的不同。
    我所表述的多重继承关系中,如果将point2d:: x ()、point2d::y()以及point2d::z() 改为virtual function。
    那么, 整个继承体系中,有三个virtual table 。
    1、point2d 的virtual table。
    它的内容是这样的:

*vptr[0]:            type_info  class object
*vptr[1]:            point2d::x()
*vptr[2]:            point2d::y()
*vptr[3]:            point2d::z()

2、point3d 的 virtual table.
     它的内容是这样的:

*vptr[0]:          type_info class object
*vptr[1]:          point2d::x()
*vptr[2]:          point2d::y()      
*vptr[3]:          point3d::z()                // 注意这里:由point3d class 改写了point2d::z()

3、vertex3d 的virtual table
     它的内容是这样的:

*vptr[0]:          type_info class object
*vptr[1]:          point2d::x()
*vptr[2]:          point2d::y()
*vptr[3]:          point3d::z()            // 继承自 point3d class

以上三个virtual table 是编译器产生的,当然都是编译期产生的,当然空间上的负担,与时间上的负担(经过间接提领)。
   而:每一个class subobject 的 vptr 设定是由 constructor 自动设定的。而本例中的 constructor 是由编译器自动扩充为:

point3d::point3d(float x = 0.0, float y = 0.0, float z = 0.0)
           : point2d(x, y), _z(z)
{
              point2d::point2d(x, y);
              _vptr = vtbl_for_point3d;
              _z = z;
}

由上所述:
   无论:

      p2d = new point3d;

还是:

      p2d = new vertex3d;

或者是:

      p2d = new point2d;

它们的调用:

         p2d->;x();

方式都是一样的:
   即:

    (*p2d->;vptr[1])(p2d);      

格式是固定的。是在编译期间设定的,所不同的只是对应的 virtual table不同,也即是vptr不同而已。而 vptr 设定是由 constructor 设定,也是编译期间设定的。而只有调整了this指针才能访问到正确的 subobject !!
   如果, class 中没定义 constructor, 将由编译器合成一个 default constructor 以正确设置 vptr。

   我不明白,你为什么说这不是负担(编译期间设定)???
   编译器经过这样的间接方式寻址到确定是执行体,产生动态效果。
   只是产生动态效果,并不是真正执行时才确定,不经过编译器的调节?那么,你的代码真神了!!

   在非 virtual 的多重继承下,必要时只需调整 this 就行了,不需要调整 vptr
   相信,在这一点上,我已经说得很明白了!!!



    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多