探索C++多态性基石:virtual 特性 C++的效率一定比C要低吗?不能这么说! CODE: class point2d { 和一个C 的 struct 代码如下:public: … … private: float _x, _y; }; class point3d : public point2d { public: … … private: float _z; }; CODE: struct point3d { 在空间与效率上并没有什么分别!但C++提供了更好的封装与继承特性 !float _x, _y, _z; } 在这里,我们来看一看,多重继承下,到底什么影响了空间与效率。 在讲virtual多重继重前,先讲一讲非virutal 的多重继承 一. 非virutal function 非 virtual base class下的多重继承 看下面代码: CODE: #include 结果是,调用vertex::foo()。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; }; 在不含virtual function 与 virtual base class的情形下,多重继承主要的效率负担是:derived class与第二个base class及后继base class(更多的base class)的this指针的调整。上例的: CODE: vertex *pv = &v3d; 为了调用vertex::foo(),编译器必须作出一些调整,将v3d的this指针调整为vertex的this指针,如何调整?编译器这样做: CODE: vertex *pv = (vertex *)(this + sizeof(point3d)); 经过这样调整后,pv可以正确寻址到foo();我们来验证一下: 我用gcc3.3.1来编译上述代码,得出如下(main()代码): CODE: 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 CODE: (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 CODE: leal -40(%ebp), %eax addl $12, %eax 上述代码: 1)在我的陈述中,我有说过上面的代码进行动态绑定吗? 那是,编译期已经确定了的。 CODE: point2d *p2d = new vertex3d; p2d->;x(); 这样的话,是不需要调整this指针,那么,在这里,就没有额外的调整动作,也就没有额外的负担。难道,在编译时候,编译器安插的动作就不算是负担! CODE: point2d *p2d = new vertex3d; p2d->;x (); 编译器怎样令p2d调用x()呢? (注意,这是编译期间的调整) CODE: (*p2d->;vptr[1])(p2d); // 调用verter3d::x() 上面执行了动态绑定。注意,这是编译期间就调整好了,格式是固定的,这就是额外的负担。 CODE: p2d = new point3d; p2d->;x (); 时,调用point3d:: x ()。那么编译的调整呢?(注意:一样的) CODE: (*p2d->;vptr[1])(p2d); // 固定的。。。。 看到没有??上面的调整和,将vertex3d object 给p2d赋值是一样的。 CODE: *vptr[0]: type_info class object *vptr[1]: point2d::x() *vptr[2]: point2d::y() *vptr[3]: point2d::z() 2、point3d 的 virtual table. CODE: *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 CODE: *vptr[0]: type_info class object *vptr[1]: point2d::x() *vptr[2]: point2d::y() *vptr[3]: point3d::z() // 继承自 point3d class 以上三个virtual table 是编译器产生的,当然都是编译期产生的,当然空间上的负担,与时间上的负担(经过间接提领)。 CODE: 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; } 由上所述: CODE: p2d = new point3d; 还是: CODE: p2d = new vertex3d; 或者是: CODE: p2d = new point2d; 它们的调用: CODE: p2d->;x(); 方式都是一样的: CODE: (*p2d->;vptr[1])(p2d); 格式是固定的。是在编译期间设定的,所不同的只是对应的 virtual table不同,也即是vptr不同而已。而 vptr 设定是由 constructor 设定,也是编译期间设定的。而只有调整了this指针才能访问到正确的 subobject !! C++的效率一定比C要低吗?不能这么说! CODE: class point2d { public: … … private: float _x, _y; }; class point3d : public point2d { public: … … private: float _z; }; 和一个C 的 struct 代码如下: CODE: struct point3d { float _x, _y, _z; } 在空间与效率上并没有什么分别!但C++提供了更好的封装与继承特性 ! CODE: #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()。 CODE: vertex *pv = &v3d; 为了调用vertex::foo(),编译器必须作出一些调整,将v3d的this指针调整为vertex的this指针,如何调整? CODE: vertex *pv = (vertex *)(this + sizeof(point3d)); 经过这样调整后,pv可以正确寻址到foo(); CODE: 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 我解释一下: CODE: (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 CODE: leal -40(%ebp), %eax addl $12, %eax 上述代码: 1)在我的陈述中,我有说过上面的代码进行动态绑定吗? 那是,编译期已经确定了的。 CODE: point2d *p2d = new vertex3d; p2d->;x(); 这样的话,是不需要调整this指针,那么,在这里,就没有额外的调整动作,也就没有额外的负担。难道,在编译时候,编译器安插的动作就不算是负担! CODE: point2d *p2d = new vertex3d; p2d->;x (); 编译器怎样令p2d调用x()呢? (注意,这是编译期间的调整) CODE: (*p2d->;vptr[1])(p2d); // 调用verter3d::x() 上面执行了动态绑定。注意,这是编译期间就调整好了,格式是固定的,这就是额外的负担。 CODE: p2d = new point3d; p2d->;x (); 时,调用point3d:: x ()。那么编译的调整呢?(注意:一样的) CODE: (*p2d->;vptr[1])(p2d); // 固定的。。。。 看到没有??上面的调整和,将vertex3d object 给p2d赋值是一样的。 CODE: *vptr[0]: type_info class object *vptr[1]: point2d::x() *vptr[2]: point2d::y() *vptr[3]: point2d::z() 2、point3d 的 virtual table. CODE: *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 CODE: *vptr[0]: type_info class object *vptr[1]: point2d::x() *vptr[2]: point2d::y() *vptr[3]: point3d::z() // 继承自 point3d class 以上三个virtual table 是编译器产生的,当然都是编译期产生的,当然空间上的负担,与时间上的负担(经过间接提领)。 CODE: 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; } 由上所述: CODE: p2d = new point3d; 还是: CODE: p2d = new vertex3d; 或者是: CODE: p2d = new point2d; 它们的调用: CODE: p2d->;x(); 方式都是一样的: CODE: (*p2d->;vptr[1])(p2d);
格式是固定的。是在编译期间设定的,所不同的只是对应的 virtual table不同,也即是vptr不同而已。而 vptr 设定是由 constructor 设定,也是编译期间设定的。而只有调整了this指针才能访问到正确的 subobject !! |
|
来自: astrotycoon > 《深度理解C 》