Data语意学

Posted along4396

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Data语意学相关的知识,希望对你有一定的参考价值。

  一、空类大小

#include<iostream>
class X
{
};

class Y:public virtual X
{
};

class Z:public virtual X
{
};

class A:public Y,public Z
{
};

class Point2d
{
        float x;
        float y;
};

class Point3d:public Point2d
{

};


int main()
{
        std::cout<<"sizeof(X): "<<sizeof(X)<<"
"
                <<"sizeof(Y): "<<sizeof(Y)<<"
"
                <<"sizeof(Z): "<<sizeof(Z)<<"
"
                <<"sizeof(A): "<<sizeof(A)<<"
";

        return 0;
}

  以上的代码,在linux上编译的输出结果为:

sizeof(X): 1
sizeof(Y): 8
sizeof(Z): 8
sizeof(A): 16

  对于以上的输出,深度探索C++对象模型的解释是:

1. c++空类对象的大小不为0,它有一个隐藏的1byte大小,用于让同一个class的两个objects在内存中有独一无二的地址

2.语言本身所造成的额外负担,若有virtual base class,那么在derived class中,将额外存在一个指针大小的空间。该指针或者指向virtual base class subobject,或者指向一个相关的表格(表格存放或者是subobject的地址,或者是subobject的偏移位置)

3.编译器对于特殊情况所提供的优化处理

4.alignment的限制

class Y对象的内存分布为:

技术图片

class Z对象的内存分布为:

技术图片

  不同的编译器对于empty virtual base classes的设计可能不同,在某些编译器中,一个empty virtual base class 被视为是derived class 的最开头的一部分,也即是说它没有花费任何额外空间,这就节省了以上提到的1byte空间爱in,也不再需要3byte的填充。

 

二、继承和Data Member

1. 只要继承不要多态

  一般而言,具体继承(concrete inheritance)相对于虚拟继承并不会增加空间或者存取时间上的负担。

class Point2d
{
public:
        Point2d(float x=0,float y=0)
                :m_x(x),m_y(y)
        {
        }
        float x() const
        {
                return m_x;
        }
        float y() const
        {
                return m_y;
        }
        void operator+=(const Point2d& rhs)
        {
                m_x += rhs.x();
                m_y += rhs.y();
        }
private:
        float m_x;
        float m_y;
};

class Point3d:public Point2d
{
public:
        Point3d(float x =0,float y=0,float z =0)
                :Point2d(x,y),m_z(z)
        {
        }
        float z() const
        {
                 return m_z;
        }
        void operator+=(const Point3d& rhs)
        {
                Point2d::operator+=(rhs);
                m_z += rhs.z();
        }
private:
        float m_z;
};

  以上对Point2d和Point3d的设计中,Point2d和Point3d对象的内存分布为

技术图片

  但两个原本独立不相关的classes被凑成一个"type/subtype",并且带有继承关系,常常容易犯下以下错误:

  1. 重复设计一些相同操作的函数,例如以上的constructor和operator+=,他们并没有设计为inline函数,Point3d的初始化操作和加法操作,将需要部分的Point2d object和部分Point3d object作为成本

  2. 把一个class分解成两层或者更多层,有可能会为了“表现class体系的抽象化”而膨胀所需的空间,例如以下的例子

class Concrete
{
private:
        int val1;
        char c1;
        char c2;
        char c3;
};
class Concrete1:public Concrete
{
private:
        char c4;
};
class Concrete2:public Concrete1
{
private:
        char c5;
};
class Concrete3:public Concrete2
{
private:
        char c6;
};

  在这样的继承体系下,每层由于继承而导致的额外的字节填充是很大的。

  

2. 加上多态

  如果座标点的加法,只能处理Point2d加Point2d,Point3d加Point3d的形式,往往不是我们要的结果。通常想要的座标加法是不管座标的类型都能正确处理的接口,我们可以在继承关系中提供一个virtual function接口。

  例如以下:

class Point2d
{
public:
        Point2d(float x=0,float y=0)
                :m_x(x),m_y(0)
        {
        }
        float x() const
        {
                return m_x;
        }
        float y() const
        {
                return m_y;
        }

        // 设定z(),operator+=()为虚函数 
        virtual float z() const
        {
                return 0.0;     // 2d点的z为0.0
        }

        virtual void operator+=(const Point2d& rhs)
        {
                m_x = rhs.x();
                m_y = rhs.y();
        }
private:
        float m_x;
        float m_y;
};

class Point3d:public Point2d
{
public:
        Point3d(float x=0,float y=0,float z=0)
                :Point2d(x,y),m_z(z)
        {
        }


        float z() const
        {
                return m_z;
        }
        void operator+=(const Point2d& rhs) // 注意参数类型为const Point2d&而不是const Point3d&
        {
                Point2d::operator+=(rhs);
                m_z = rhs.z();
        }
private:
        float m_z;
};

  以上的做法使得Point2d有了额外的空间和存取时间负担。

  1.导入和Point2d有关的virtual table,用来存放它所声明的每一个virtual function的地址。

  2.在每个class object中导入vptr,提供执行期的链接,使得每个object能找到相应的virtual table.

  3.改变constructor,使得它能够设定vptr的值,让它指向class对应的virtual class.

  4.改变destructor,使得它能抹消“指向class相关的virtual table”的vptr。

  以上声明的Point2d和Point3d的内存布局为:

技术图片 

vptr可能放在class object的开始处,也可能放在class object的结尾处,具体的位置视编译期的实现而定。

 

3.多重继承

 

以上是关于Data语意学的主要内容,如果未能解决你的问题,请参考以下文章

第 3 章 Data语意学

function语意学

第 4 章 Function语意学

深度探索C++对象模型——Function语意学

深度探索C++对象模型——Function语意学

:构造函数语意学之Default constructor的构造操作