《深度探索C++对象模型》:member functions

本文深入探讨了C++中虚函数的实现机制,包括单一继承、多重继承和虚拟继承下的表现形式,解释了如何通过vptr和vtbl定位并调用正确的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        在第一篇文章中我们谈到member function有三种:nonstaticstaticvirtual。前面两种并不是我们今天的主角,我们要学习的是virtual member functions。

nonstatic member functions

        C++的设计准则之一就是:nonstatic member function至少必须和一般的nonmember function有相同的效率,实际上member function被编译器内化为nonmember的形式。member function的调用必须经由一个class object才能操作,这点与static mmber function不同。

static member functions

一个static member function的主要特性是它没有this指针,并且由此衍生出来的其他特性有:

        1、它不能够直接存取class中的nonstatic member;

        2、它不能够被声明为const、volatile或virtual;

        3、它的调用不需要经由class object(虽然大部分时候它是这么被调用的)。

// 如果Point3d::normalize()是一个static member function
obj.normalize();
ptr->normalize();
// 那么以上两个调用操作,将会被转换为一般的nonmember函数的调用。

单一继承下的virtual functions

        在之前的文章中,我们或多或少的了解了virtual的一般实现模:每一个class有一个vtbl,内含class的virtual functions的地址,每个class object有一个vptr,指向vtbl
我们来看一个例子:

class Point {
public:
    virtual ~Point();
    virtual Point& mult(float) = 0;
    // ...
    float x() { return _x; }
    virtual float y() const { return 0.0F; }
    virtual float z() const { return 0.0F; }
    // ...
protected:
    Point(float x = 0.0F);
    float _x;
};

class Point2d : public Point {
public:
    Point2d(float x = 0.0F, float y = 0.0F) : Point(x), _y(y) {}
    ~Point2d();

    Point2d& mult(float);
    float y() const { return _y; }
protected:
    float _y;
};

        显然这是一个单一继承的例子,很简单。那么就有一个问题来了,当我们调用某个virtual member function譬如说ptr->y()的时候,那我们调用的是classPointobjecty()还是classPoint2dobjecty()呢?也就是说,y()是一个virtual member function,我们知道RTTI的特性就是某些信息只有在执行期才能确定,而在编译期是不能确定的。

        显然,不管编译期还是执行期,若我们要正确的执行y()的实例,那么整个过程中需要知道:

                1、ptr所指对象的真实类型;

                2、y()实例的位置。

        根据前面几节对象模型学习,我们知道在C++对象模型中,在编译期编译期会为我们构建两个东西:vptrvtbl,在程序执行时表格的大小和内容是不会发生改变的。由于要执行y()函数,可以由两个步骤来完成这项任务(即vptrvtbl的构建):

               1、为了找到表格,每一个class object被安插了一个由编译器内部产生的指针,指向该表格;

                2、为了找到该函数地址,每一个virtual function被指派一个表格索引值。

        当然,以上的工作都是有编译器在编译期完成的,那么在执行期所要做的就是,只在特性的virtual table slot中激活virtual function。

        根据上面的对象模型,我们在编译期就构建了vptr和vtbl,而且已经知道了某个函数的索引值,因此唯一一个在执行期才能知道的东西就是:slot所指的到底是哪一个函数实例,当然执行期ptr的具体指向会为我们解决这个问题。

class Point3d : public Point2d {
public:
    Point3d(float x = 0.0F, float y = 0.0F, float z = 0.0F) : Point2d(x, y), _z(z) {}
    ~Point3d();

    Point3d& mult(float);
    float z() const { return _z; }
    // ...
protected:
    float _z;
};


在单一继承中,virtual function机制的行为非常良好的,不但有效率而且很容易被塑模出来。

多重继承下的virtual functions

对于多重继承,我们还是直接来看一个例子。

class Base1 {
public:
    Base1();
    virtual ~Base1();
    virtual void speakClearly();
    virtual Base1* clone() const;
protected:
    float data_Base1;
};

class Base2 {
public:
    Base2();
    virtual ~Base2();
    virtual void mumble();
    virtual Base1* clone() const;
protected:
    float data_Base2;
};

class Derived : public Base1, public Base2 {
public:
    Derived();
    virtual ~Derived();
    virtual Derived* clone() const;
protected:
    float data_Derived;
};


在多重继承之下,对象模型的机制是:一个derived table slot内含n-1个额外的virtual tables,n表示其上一层base classes的个数(因此,单一继承将不会有额外的virtual tables)。对于本例的Derived而言,会有两个virtual tables被编译器产生出来:

        1、一个主要实例,与Base1(最左端base class)共享;

        2、一个次要实例,与Base2(第二个base class)有关。

针对每一个virtual tables,Derived对象中有对应的vptr,具体对象模型如下:


虚拟继承下的virtual functions

在之前的文章中,我们大概也见识到了虚拟继承的特殊性,class subobject作为共享部分放在最后面,而在vtpr所指向的vtbl中的开头添加了表示offset的项。下面还是来看一个现实例子。

class Point2d {
public:
    Point2d(float = 0.0F, float = 0.0F);
    virtual ~Point2d();

    virtual void mumble();
    virtual float z();
    // ...
protected:
    float _x;
    float _y;
};

class Point3d : public virtual Point2d {
public:
    Point3d(float = 0.0F, float = 0.0F, float = 0.0F);
    virtual ~Point3d();

    virtual float z();
    // ...
protected:
    float _z;
};
其对象模型如下:


这样一来我们就清楚了虚拟继承下的对象模型。

有一点建议就是:不要再一个virtual base class中声明nonstatic data members


最后,传一个在学习《深度探索C++对象模型》过程中记录的一些模型图,在这里:http://download.csdn.net/detail/hujingshuang/9663843


参考资料:

[1] 深度探索C++对象模型,[美]Stanley B. Lippman著,侯捷译;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值