您的位置:首页 > 编程语言 > C语言/C++

《深度探索c++对象模型》 学习笔记 - 4 Function语义学

2009-02-19 10:35 155 查看
4 Function语义学
1. C++标准要求:非静态成员函数至少必须和一般的非成员函数有相同的效率。
2. 成员函数会被编译器mangle为非成员函数,尤其是重载更需要mangle手法进行改名。extern “C”阻止函数改名。
3. Static成员函数:1)参数没有this;2)不能访问非静态成员;3)不能是const、virtual等;4)可以不用对象访问。
4. Static成员函数,由于没有this,所以可以作为callback函数的候选,或者作为线程的主函数。
5. C++中多态表示:以一个public base class的指针或引用,寻址出一个derived class对象,并访问相应的virtual函数。
6. Vtbl中的虚函数一定在编译期间获知,其函数的个数、位置和地址是固定不变的,执行期间不能增、改、删。
7. 执行期三步完成虚函数调用:1)由vptr找到vtbl;2)定位vtbl中的slot(索引值);3)通过该索引下的值调函数。
8. Vtbl的内容包括三部分:1)在该类中定义的函数实体,它是一个新的virtual函数,或者会override其基类的虚函数;2)继承自基类的虚函数实现,本类不进行override;3)纯虚函数的占位。注意:普通非virtual函数不在vtbl中。
9. 子类新增加virtual函数的时候,会在原本的vtbl中最后面增加一个slot,加入这个新的virtual函数地址。
10. 多重继承中,一个派生类会有n-1个额外vtbl(n=直接基类个数),它与第一父类共享vtbl,会修改其他父类的vtbl。
11. 在这三种情况下,第二或后继基类会影响virtual函数的调用:
Ø 通过“指向第二个基类”的指针,调用派生类的自己新的虚函数。这时,指针必须调整到Derived的vptr处。
Ø 通过“指向派生类”的指针,调用从第二个基类中派生下来的虚函数。这时,指针必须调整到第二基类vptr处。
Ø 允许从基类派生的虚函数修改返回值的情况,在VC中不支持,所以具体情况未知。
12. 虚拟继承下的虚函数,由于有了一个到虚拟基类的offset,所以virtual函数的调用复杂度更高,性能更差。
13. A)指向非static成员变量的指针,其值是它在该类内存中的offset:float ClassA::*pt1 = &ClassA::m_f1;B)1)指向静态成员函数的指针,其值是该函数在内存中的真正地址;2)指向virtual成员函数的指针,其值是vtbl中的索引值;3)指向非virtual非静态成员函数的指针,其值是该函数在内存中的真正地址,绑定到this之后即可以进行函数调用。
Inline对编译器只是请求,并非命令。inline中的局部变量+有表达式的参数 => 大量临时变量 => 程序规模暴涨。

实验代码:

#ifndef ch4_h
#define ch4_h

namespace ch4{  // Data semantic

// p145
namespace nameMangle{

// 关于mangling和extern "C"
// No1和No2可以永远共存,并可以和No3或No4共存
// No3和No4不能共存,因为:
// error C2733: second C linkage of overloaded function 'f1' not allowed
// 就是说,作为C语言的函数,不能重载。

// 但是奇怪的是,No3和No1就不算重载吗?
// A:应该算重载,但是No1改了名字而No3没有改,所以没问题
int f1(){return 1;};                    // NO1
int f1(int){return 1;};                 // NO2
extern "C" int f1(double){return 1;};   // NO3
// extern "C" int f1(char){return 1;};     // NO4

class  ccclass{
public:
// 在类的里面不能使用extern "C"来阻止name mangling
// extern "C" int x(){ return 0;};
int y(){return 1;};

};
void test(){
int n = f1();
n = f1(10);
n = f1(.1);
}
}

// p165
namespace vtable{

// 根据看Derived的类中的两个vptr(Base1带来一个,Base2带来一个)
// 发现其vtbl的构造,与书上所说不一样,书上的部分肯定有错误,尤其是
// Base1::vtbl中居然有Base2::mumble,简直不可能。
// 正确结果如下(太明显了,就不解释了;typeid没有显示出来):
/*
-	ch4::vtable::Base1	{...}
-	__vfptr	0x0042a2b0 const ch4::vtable::Derived::`vftable'{for `ch4::vtable::Base1'}
[0]	0x004011d1 ch4::vtable::Derived::`scalar deleting destructor'(unsigned int)
[1]	0x004011e5 ch4::vtable::Base1::speakClearly(void)
[2]	0x00401226 ch4::vtable::Derived::clone1(void)

-	ch4::vtable::Base2	{...}
-	__vfptr	0x0042a158 const ch4::vtable::Derived::`vftable'{for `ch4::vtable::Base2'}
[0]	0x004011d6 [thunk]:ch4::vtable::Derived::`vector deleting destructor'`adjustor{8}' (unsigned int)
[1]	0x0040120d ch4::vtable::Base2::mumble(void)
[2]	0x00401221 ch4::vtable::Derived::clone2(void)
*/
class Base1{
public:
Base1(){};
virtual ~Base1(){};
virtual void speakClearly(){};
virtual void clone1() const{};
int m_base1;
};
class Base2{
public:
Base2(){};
virtual ~Base2(){};
virtual void mumble(){};
virtual void clone2() const{};
int m_base2;
};
class Derived: public Base1, public Base2 {
public:
Derived(){};
virtual ~Derived(){};
virtual void clone1() const{};
virtual void clone2() const{};
int m_Derived2;
};
void test(){
Derived d;
}

}

// 第三章的p130: 指向数据成员的指针。可以用来研究内存布局
// 本来应该放到第三章,但是第四章有非常详细的说明,所以就写到一起了。
namespace point2d3d{
// 内存布局:
// vptr(4),x(4),y(4)
class p2d{
public:
int x;
int y;

virtual void f(){};
};

// 内存布局:
// vptr(4), next(4), p2d[vptr(4),x(4),y(4)]

// 对于virtual继承,子类和基类共享同一个vptr,
// 不过,由于有了offset pointer,这样就增加了size;
class vertex:public virtual p2d{
public:
vertex* next;
};

// 内存布局,同vertex:
// vptr(4), z(4), p2d[vptr(4),x(4),y(4)]
class p3d:public virtual p2d{
public:
int z;
};

// 内存布局:vertex + p3d - p2d
// vertex[vptr(4), next(4)], p3d[vptr(4), z(4)], dummy(4), p2d[vptr(4),x(4),y(4)]

// 对于普通继承,子类会和基类共享同一个vptr,换句话说,不会由于virtual func而增加size;
class pointvertex3d:public vertex, public p3d {
public:
int dummy;
};

void test()
{

// 指向数据成员的指针,其值是该对象(变量)在class object中的内存地址位置偏移量,
// 由于都是int、*、vptr等,所以,下面的值都是0,4,8等,4的倍数
int p2d::*n = 0;
n = &p2d::x;
n = &p2d::y;

int p3d::*n2 = 0;
n2 = &p3d::x;
n2 = &p3d::y;
n2 = &p3d::z;

int vertex::*n3 = 0;
n3 = &vertex::x;
n3 = &vertex::y;

vertex* vertex::*nv = 0;
nv = &vertex::next;

int pointvertex3d::*n4 = 0;
n4 = &pointvertex3d::x;
n4 = &pointvertex3d::y;
n4 = &pointvertex3d::z;
n4 = &pointvertex3d::dummy;

vertex* pointvertex3d::*nv2 = 0;
nv2 = &pointvertex3d::next;

// 输出函数指针,用%p
printf("pointvertex3d::next: %p", nv2);
}
}

// main test
void test(){
printf("ch4 begin/n");
nameMangle::test();
vtable::test();
point2d3d::test();
printf("ch4 end/n");
}

};
#endif
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: