C++空类的研究

C++空类的研究

 

1.C++空类及没有成员变量的类的大小实例分析
众所周知的C++中空类的大小为1,但是除了空类之外的其他一些没有成员变量的类的大小,还是有相当一部分开发人员对此有很多不明之处的。

这里我们以如下代码为例:

 

  1.  
    #include <iostream>
  2.  
    using namespace std;
  3.  
     
  4.  
    class a {};
  5.  
    class b {
  6.  
    public:
  7.  
    void test() { cout << "test" << endl; }
  8.  
    };
  9.  
    class c :public a {
  10.  
    virtual void fun() = 0;
  11.  
    };
  12.  
    class d :public b, public c {};
  13.  
    int main() {
  14.  
    cout << "sizeof(a)" << sizeof(a) << endl;//1, 为了保证每个类在内存中都有一个独一无二的地址
  15.  
    cout << "sizeof(b)" << sizeof(b) << endl;//1, 成员函数不占用内存
  16.  
    cout << "sizeof(c)" << sizeof(c) << endl;//4, 虚函数表指针占用4个字节
  17.  
    cout << "sizeof(d)" << sizeof(d) << endl;//8, 内存对齐
  18.  
    getchar();
  19.  
     
  20.  
    return 0;
  21.  
    }

为什么会出现这种结果呢?初学者肯定会对此疑惑不解,类a,b明明是空类,它的大小应该为为0,为什么编译器输出的结果为1呢?这就是我们刚才所说的实例化的原因(空类同样可以被实例化),每个实例在内存中都有一个独一无二的地址,为了达到这个目的,编译器往往会给一个空类隐含的加一个字节,这样空类在实例化后在内存得到了独一无二的地址,所以a,b的大小为1。
而类c是由类a派生而来,它里面有一个纯虚函数,由于有虚函数的原因,有一个指向虚函数表的指针(vptr,有多个虚函数仍然是只有一个指针),在32位的系统分配给指针的大小为4个字节,所以最后得到c类的大小为4。

类d的大小更是会让很多的初学者疑惑,类d是由类b,c派生而来的,它的大小应该为二者之和5,为什么却是8呢?这是因为为了提高实例在内存中的存取效率,内存中会有数据对齐,于是类的大小往往被调整到4字节的整数倍。并采取就近的法则,往大的方向取最近的倍数,就是该类的大小,所以类d的大小为8个字节(假如d由3个空类和c派生而来,仍然是8)。 

 

空类的作用

 

  1.  
    class Empty
  2.  
    {
  3.  
    public:
  4.  
    Empty(){} //允许子类调用
  5.  
    ~Empty(){}
  6.  
    private:
  7.  
    Empty(const Empty&); //禁止复制
  8.  
    Empty& operator = (const Empty&);//禁止赋值
  9.  
    };
  10.  
     
  11.  
     
  12.  
    class SomeClass: private Empty
  13.  
    {
  14.  
    ...
  15.  
    };

由以上示例可以看出,如果我们有些类不想被复制或赋值,可以通过从Empty继承来实现。

 

2.在C++中,编译器会为空类提供哪些默认成员函数?分别有什么样的功能呢? 

空类,声明时编译器不会生成任何成员函数,只会生成1个字节的占位符。
有时可能会以为编译器会为空类生成默认构造函数等,事实上是不会的,编译器只会在需要的时候(当空类定义一个对象)生成6个成员函数:一个缺省的构造函数、一个拷贝构造函数、一个析构函数、一个赋值运算符、一对取址运算符和一个this指针。
当空类定义一个对象时sizeof(Empty)仍是为1,但编译器会生成6个成员函数:一个缺省的构造函数、一个拷贝构造函数、一个析构函数、一个赋值运算符、两个取址运算符。  

class Empty  {}; 

等价于 

 

  1.  
    class Empty
  2.  
    {
  3.  
    public:
  4.  
    Empty(); //缺省构造函数
  5.  
    Empty(const Empty &other); //拷贝构造函数
  6.  
    ~Empty(); //析构函数
  7.  
    Empty& operator=(const Empty &other); //赋值运算符
  8.  
    Empty* operator&(); //取址运算符
  9.  
    const Empty* operator&() const; //取址运算符(const版本)
  10.  
    };

 

使用时的调用情况:

  1.  
    Empty *e = new Empty(); //缺省构造函数
  2.  
    delete e; //析构函数
  3.  
    Empty e1; //缺省构造函数
  4.  
    Empty e2(e1); //拷贝构造函数
  5.  
    e2 = e1; //赋值运算符
  6.  
    Empty *pe1 = &e1; //取址运算符(非const)
  7.  
    const Empty *pe2 = &e2; //取址运算符(const)

 

C++编译器对这些函数的实现:

 

  1.  
    inline Empty::Empty() //缺省构造函数
  2.  
    {
  3.  
    }
  4.  
    inline Empty::~Empty() //析构函数
  5.  
    {
  6.  
    }
  7.  
    inline Empty *Empty::operator&() //取址运算符(非const)
  8.  
    {
  9.  
    return this;
  10.  
    }
  11.  
    inline const Empty *Empty::operator&() const //取址运算符(const)
  12.  
    {
  13.  
    return this;
  14.  
    }
  15.  
    inline Empty::Empty(const Empty&) //拷贝构造函数
  16.  
    {
  17.  
    //对类的非静态数据成员进行以"成员为单位"逐一拷贝构造
  18.  
    //固定类型的对象拷贝构造是从源对象到目标对象的"逐位"拷贝
  19.  
    }
  20.  
     
  21.  
    inline Empty& Empty::operator=(const Empty&) //赋值运算符
  22.  
    {
  23.  
    //对类的非静态数据成员进行以"成员为单位"逐一赋值
  24.  
    //固定类型的对象赋值是从源对象到目标对象的"逐位"赋值。
  25.  
    }

 例如:m是类C中的一个类型为T的非静态成员变量,若C没有声明拷贝构造函数(赋值运算符), m将会通过T的拷贝构造函数(赋值运算符)被拷贝构造(赋值);该规则递归应用到m的数据成员,直到找到一个拷贝构造函数(赋值运算符)或固定类型(例如:int、double、指针等)为止。

posted @   unicornsir  阅读(678)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示