[温故而知新] 《深度探索c++对象模型》——对象方法成员

本节的复杂点,在于对virtual function的支持上。

先从简单的几种function入手

1.non member function
2.static member function
3.non static member function

第一种是最常见的 non member funcion:

//在一个地方定义
returnValue functionName(argumentList){
    //function body
}

//在另一个地方声明
extern returnValue functionName(argumentList);

第二种static member function

class ClassName{
public:
    static returnValue functionName(argumentList);
}

//调用
ClassName::functionName(...);

static member function 与 non member function非常相似,实际上,编译器就是通过类名和函数签名等把static member function 转换为 non member function。

第三种 non static member function

class ClassName{
public:
    returnValue functionName(argumentList);
}

第三种与第二种也是非常类似,最终编译器也是把它转为一个non member function。与static member function 区别在于,non static member function 会在转换的过程中修改函数的签名,加入”this”指针。

比如:

class Point{
public:
    int x;
    int y;
    void setX(int n);
};
void Point::setX(int n){
    x = n;
}
//转化为类似如下的方法,下面的函数名命名规则,各家编译器不尽相同:
void Point_setX_int(Point *const this,int n){
    this->x = n;
}
//有了上面这一步的转化,那么那些执行方法调用的地方,编译器也会进行修改:
Point point;
point.setX(1);   //转化为类似 Point_setX_int(&point,1);

上面三种方式,效率是一致的。

virtual member functions
2.1、单继承下的virtual member functions
class Animal{
public:
    virtual void say() =0; //纯虚函数
};
class Dog:public Animal{
public:
    void say();
};
class Cat:public Animal{
public:
    void say();
};
void Dog::say(){
    cout << "wang~ wang~ wang~" << endl;
}
void Cat::say(){
    cout << "miao~ miao~ miao~" << endl;
}

画个图了解下内存布局:

这里写图片描述

注意上图种的vptr table,这些是编译器根据我们的代码生成的辅助结构,vptr table存的是一些函数指针。

这里我们假设:

//Dog::say() 编译器转化后的non member funtion为 
void Dog_say_void(Dog const *thiz){
    cout << "wang~ wang~ wang~" << endl;
}
//Cat::say() 编译器转化后的non member function为
void Cat_say_void(Cat const *thiz){
    cout << "miao~ miao~ miao~" << endl;
}

Dog dog;
Cat cat;
Animal *ptr;

//1.使用指针调用的虚函数
ptr = &dog;
ptr->say();            //wang~ wang~ wang~

ptr = &cat;
ptr->say();            //miao~ miao~ miao~

/** 
ptr->say()的编译器转化
(*ptr->vptr[index])(ptr)
*/

//2.使用对象调用的虚函数
dog.say();//编译器转化为 Dog_say_void(&dog);
cat.say();//编译器转化为 Cat_say_void(&cat);

注意比较上面两种调用方式,使用指针调用virtual 方法,可能需要经过一层间接查表得到最终调用的方法,因为在编译期无法确定ptr指向的对象实际是哪种类型,而使用对象调用virtual 方法,则不需要中间那一层查表,因为编译器在编译期就已经知道这个对象是哪种类型。

2.2、多继承下的virtual member functions
class Base1{
public:
    virtual ~Base1();
    virtual void speakClearly();
    virtual Base1 *clone() const;
protected:
    float data_Base1;
};
class Base2{
public:
    virtual ~Base2();
    virtual void mumble();
    virtual Base2 *clone() const;
protected:
    float data_Base2;
};
class Derived :public Base1, public Base2{
public:
    virtual ~Derived();
    virtual Derived *clone()const;
protected:
    float data_Derived;
};

画个图了解下内存布局:

这里写图片描述

注意上图中右下角 Derived的内存布局,是优化后的,也就是把Derived 的 vptr去掉,与Base1用同一个vptr,同时修改vptr table。

同时注意到,在上图的多重继承中的Derived,有三处地方需要涉及到“this”指针的调整。
1.virtual destructor
2.多重继承中第二个或之后的Base Class 继承下来的virtual function。这里的例子为图中标红的Base2::mumble()。
3.一个语言扩充性质:允许一个virtual function的返回值类型有所变化,可能是base type,也可能是 public derived type。 这里的例子为图中标红的clone()方法。

为什么需要调整this指针呢?因为non member function的第一个参数为this指针,比如

Base2 *ptr = new Derived;
delete ptr;
//这里实际调用的是Derived::~Derived()而不是Base2::~Base2();
//也就是实际上的调用类似 Derived_destrutor(ptr),显然这里的ptr是不对的,需要调整为Derived_destrutor(ptr-sizeof(Base1))

关于this指针的调整,书中提到微软采用了”thunk”技术,我根据书中例子画了个图,更容易理解:
这里写图片描述

上图中,红色虚框中就用到了“thunk”。

那么什么是 “thunk”技术呢?
简单地理解就是一个代码片段,这个代码片段的目的就是修改某些参数,然后通过一个jmp指令跳转到目标的代码去。
具体可以参考:
《C++ Tips: Adjustor thunk: what is it, why and how it works》
《Adjustor thunks》
《C++ 的THUNK技术》

2.3、虚拟继承下的virtual member functions

这个与多重继承下的virtual member functions类似。
书中提到一个建议:“不要在一个virtual base class 中声明nonstatic data members”。

指向 member functions 的指针

本节的复杂点,也是在于virtual member function 在多重继承和虚拟继承下的支持。
比如:

class Point{
public:
    float x();
    virtual float z();
};

//
Point *ptr = new Point;

float (Point::*pmf)() = &Ponit::z;
(ptr->*pmf)();//编译器转化为:(*ptr->vptr[(int)pmf])(ptr)

pmf = &Point::x;
(ptr->*pmf)();//编译器转化为:(*pmf)(ptr)

可以看到,同样是执行(ptr->*pmf)(),编译器要根据情况进行不同的转化。书中提到了一个结构用于支持member function指针:

struct _mptr{
    int delta;
    int index;
    union{
        ptrtofunc faddr;
        int v_offset;
    }
}

书中也没详细讲解这一节,不过可以简单的理解,就是在处理上面提到的各种对于member function pointer的支持,使用这个结构中相应的字段进行标识、支持即可。

inline function

注意几个副作用:

//1.形式参数的副作用
inline int min(int i,int j){
    return i<j?i:j;
}

int minval;
minval = min(foo(),bar()+1);
//转化为
int t1;
int t2;
minval = (t1 = foo(),t2 = bar()+1),t1<t2?t1:t2;

//2.局部变量
inline int min(int i,int j){
    int minval = i<j?i:j;
    return minval;
}

int m;
m = min(val1,val2);
//转化为
int __min_minval;
m = (__min_minval = val1<val2?val1:val2),__min_minval;
const相关小知识点
class Point{
public:
    int getX() const;  //限制该方法只读取而不修改Point对象相关数据
    void setX(int n);
private:
    int x;
}

//限制ptr不能被修改,比如不允许 ptr = otherPtr;
Point *const ptr

//限制ptr指向的对象不能被修改,比如不允许调用 ptr->setX(0)
const Point *ptr

//限制ptr不能被修改,并且不能修改ptr指向的对象。
const Point *const ptr
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值