c++ ---深度探索C++对象模型读书笔记4

本文深入探讨了C++中成员函数的各种类型,包括非静态成员函数、静态成员函数及虚函数,并详细解释了它们的工作原理和技术细节。

Function 语言学

Nonstatic Member Function

       非静态成员函数至少必须和一般的nonmember function有相同的效率。编译器将“member函数实体”转换为对等的“nonmember函数实体”。

成员函数会在内部被转化为非成员函数形式:

  1. 改写函数的原型,以传入一个额外的参数this指针,到member function 中,用以提供一个存取管道,是class object得以调用该函数。
  2. 将每一个“对nonstatic data member的存取操作”改为经由this指针来存取。
  3. 将member function 重新写成一个外部函数,对函数名称进行“mangling”处理(一般在名称前面加上class名称和参数链,形成独一无二的命名)。

 

Virtual Member Functions

Vptr标识由编译器产生的指针,每一个类有一个virtual table,内含该class 之中有作用给他virtual function的地址,然后每一个object有一个vptr,指向virtual table。       Ptr->normalize();

将会被内部转化为:

       (*ptr->vptr[1])(ptr);  //1是virtual table slot的索引值,关联函数;第二个ptr标识this指针。

this指针的特性:

1、this指针的类型 类类型* const

2、this指针并不是对象本身的一部分,不影响sizeof的结果。

3、this的作用域在类成员函数的内部。

4、this指针是类成员函数的第一个默认隐含参数,编译器自动维护传递,类编写者不能显式传递。

5、只有在类的非静态成员函数中才可以使用this指针,其它任何函数都不可以。

6、如果参数个数确定,this指针通过寄存器ecx传递给被调用者,如果参数个数不确定,this指针在所有参数被压栈后压入堆栈

 

 

Static member function

不会有:1.能够直接存取非静态的数据。2.被声明为const。

主要特性是没有this指针,次要特性:

不能直接存取其class中的非静态成员。

不能被声明为const 、volatile或者virtual。

不需要一定经由class object才被调用。

如果取一个静态成员函数的地址,获得的将是其在内存中的地址,由于静态成员函数没有this指针,所以其地址的类型并不是一个智子昂class member function的指针,而是一个nonmember 函数的指针。

      

 

Virtual Member Functions

1)虚函数按照其声明顺序放于表中。

2)父类的虚函数在子类的虚函数前面。

一般继承(无虚函数覆盖)

注意到:
1. 虚函数按照其声明顺序放于表中。
2. 父类的虚函数在子类的虚函数前面。

一般继承(有虚函数覆盖)

注意到:
1. 覆盖的f()函数被放到了虚表中原来父类虚函数的位置。
2. 没有被覆盖的函数依旧。
因此对于程序:
Base *b = new Derive();
b->f();
由b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代,于是在实际调用发生时,是Derive::f()被调用了。这就实现了多态。

多重继承(无虚函数覆盖)

注意到:
1. 对于实例Derived d的对象,每个父类都有存有一个指针,指向对应的虚函数表。
2. 子类的成员函数被放到了第一个父类的表中。(第一个父类是按照声明顺序来判断的)

多重继承(有虚函数覆盖)

虚继承

在谈虚继承前,我们先看这样一段代码:

class B
{
public:
    int _b;
};
class C1 : public B
{
public:
    int _c1;
};
class C2 : public B
{
public:
    int _c2;
};
class D :public C1, public C2
{
public:
    int _d;
};
 
int main()
{
    D d;  
    //d._b = 10;错误,访问不明确  
    d.C1::_b = 10;//正确  
    d.C2::_b = 10;//正确  
    return 0;
}

为什么会出现这样的问题?
我们知道它们的继承层次如下图所示:

这种看似菱形的多继承会带来二义性:也就是说D中_b到底是从C1这条路继承而来的还是从C2这条路继承而来的?C++中为了避免这种访问不明确,从而引入了虚拟继承的机制。
虚拟继承是多重继承中特有的概念。虚拟基类是为解决多重继承而出现的。如上述类D继承自类C1、C2,而类C1、C2都继承自类B,因此在类D中两次出现类B中的变量。为了节省内存空间,可以将C1、C2对B的继承定义为虚拟继承,而B就成了虚拟基类。实现代码如下:

class B
{
public:
    int _b;
};
class C1 :virtual public B
{
public:
    int _c1;
};
class C2 :virtual public B
{
public:
    int _c2;
};
class D :public C1, public C2
{
public:
    int _d;
};
 
int main()
{
    D d;
    d._d = 4;
    return 0;
}

这样就可以达到我们的要求了,直接使用d._d访问到_d。然而虚继承到底是一种怎么样的实现机制?我们不妨在加不加virtual这两中情况下看下在内存中D d这个对象模型是怎么样的?
对于普通继承,我们通过VS2013的内存窗口可以看到:

先是C1类中的成员,再是C2类中的成员,最后是D类自己的成员,此时sizeof(D) = 20。而一旦加了虚继承了,变化就比较明显了,如下图:

最后再看几道有关的虚继承的题目:

对这四种情况分别求sizeof(a), sizeof(b)。结果是什么样的呢?我在VS2013的win32平台测试结果为:
第一种:4,12
第二种:4,4
第三种:8,16
第四种:8,8
这是为什么???首先我们看a类,我们知道每个存在虚函数的类都要有一个4字节的指针指向自己的虚函数表,再加上如果有数据,根据内存对齐机制,四种情况的类a所占的字节数应该是没有什么问题的。我们再看sizeof(B),我们先看普通继承,对于普通继承仅仅是在原来的基础对虚表指针指向的虚函数表进行改写,类B依旧只有一个虚表指针,再加上如果有数据,根据内存对齐机制,所以第二种和第四种情况下,sizeof(B)分别为4和8。然而对于虚拟继承,会增加了一个偏移指针,而且由于类B中新增了虚函数,所以它的一般对象模型为这样(具体为什么是这样本文菱形虚拟继承会讲):

根据图示,在第一种的情况下,由于没有对应的数据成员,所以大小为12个字节。在第三种情况下,子类有自己的数据成员,而基类没有,所以删去最后一项,大小就是16个字节了。

 

参考:https://blog.youkuaiyun.com/xy913741894/article/details/52981011

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值