文章插图
什么是C++的空类顾名思义 , 空类就是指哪些不包含成员变量的类 。例如以下这个就是一个空类:
class EmptyBase {};
既然如此 , 那么是不是说空类的内部一定不会其他代码呢?不是的,空类内部也可以包含其他东西,例如:构造函数、析构函数、静态成员变量、静态函数、成员函数、typedef语句等 。例如在以下代码中EmptyBase依然是空类:
class EmptyBase {public:// 构造函数EmptyBase(){}// 析构函数~EmptyBase(){}// typedef并没有给类增加成员或者函数typedef int INT_NUM;// 不涉及到内部成员变量的内部函数void set(int a){}// 静态函数static void setStr(const std::string& s){}// 静态变量static std::string str;};
在C++11之后我们可以使用std::is_empty判断一个类是否是空类:#include <IOStream>class EmptyBase {};int mAIn() {auto aa = std::is_empty<EmptyBase>::value;std::cout << "是否是空类:" << aa << std::endl;return 0;}
C++空类的大小有以下计算空类大小的代码,你认为输出结果是多少?#include <iostream>class EmptyClass {// 空类};int main(int argc, char* argv[]) {std::cout << "sizeof(EmptyClass): " << sizeof(EmptyClass) << std::endl;return 0;}
即使是空类,其大小也不会为0 。在许多平台上,空类的大小为1;而在某些对于对齐(alignment)要求更严格系统上,空类的大小可能是另一个数(通常是4) 。为什么C++空类的大小不是0呢?
C++的设计者们不允许类的大小为0 , 因为每个对象都必须具有唯一的地址,特别是在涉及到取址和指针计算时,如果一个类的大小是0,那么指针的一切将会失效 。试想一下如果空类的大小为0,那么由空类它们构成的数组,其大小必然也是0,这会导致指针运算中普遍使用的性质失效 。
空基类优化C++标准规定,当空类作为基类时,只要不会与同一类型的另一个对象或子对象分配在同一地址 , 就不需为其分配任何空间 。
#include <iostream>class EmptyBase {// 空基类};class EmptyOne: public EmptyBase{// 空类1};class EmptyTwo: public EmptyOne{// 空类2};int main(int argc, char* argv[]) {std::cout << "sizeof(EmptyBase): " << sizeof(EmptyBase) << std::endl;std::cout << "sizeof(EmptyOne): " << sizeof(EmptyOne) << std::endl;std::cout << "sizeof(EmptyTwo): " << sizeof(EmptyTwo) << std::endl;return 0;}
如果编译器支持空基类优化,上述程序所有的输出结果相同(一般是1) , 但均不为0 。我们修改一下代码,将EmptyTwo改为多继承,那么EmptyTwo还是空类吗?
class EmptyTwo: public EmptyOne,public EmptyBase{};
答案是在多继承状态的EmptyTwo已经不是空类了 , 虽然EmptyTwo和它的基类都没有任何成员 。不过,EmptyTwo的基类EmptyOne和EmptyBase不能分配到同一地址空间, 否则EmptyTwo的基类EmptyBase会和EmptyOne的基类EmptyBase撞在同一地址空间上 。换句话说,两个相同类型的子对象偏移量相同,这是C++对象布局规则不允许的 。对空基类优化进行限制的根本原因在于 , 我们需要能比较两个指针是否指向同一对象 。由于指针几乎总是用地址作内部表示 , 所以我们必须保证两个不同的地址(即两个不同的指针值)对应两个不同的对象 。虽然这种约束看起来并不非常重要,但是在实际应用中的许多类都是继承自一组定义公共typedefs的基类 , 当这些类作为子对象出现在同一对象中时,问题就凸现出来了,此时优化应被禁止 。
空类存在的意义是什么尽管在面向对象编程中,空类看起来可能有些多余 , 但是它们存确有它们的用途 。
空类是一种有着潜在应用价值的编程技巧,例如空类可以被用于多种编程模式和设计模式中,它还可以作为数据类型的标记 , 用于在编译时实现条件编译 。空类也可以作为接口占位符,用于后续的继承实现或者后续扩展等 。空类也在模板编程和元编程等高级编程技术中也发挥重要作用 。
例如在C++标准库中 , 五种迭代器类别都有对应的空类 。这些空类用于标识迭代器的类别,并通过模板特化来实现对不同类型迭代器的特殊处理,如图:
文章插图
【C++空类的那点事儿】
推荐阅读
- 四个解决特定的任务的Pandas高效代码
- 解锁 C++ 并发编程的钥匙:探索 Atomic 变量
- 深入了解Linux中常见的五种文件类型
- 一次性讲清楚「连接池获取连接慢」的所有原因
- 如何优雅的组织Golang项目结构
- SQL连接最全总结:提升你的数据库查询技能
- 浏览器中存储访问令牌的最佳实践
- 特殊线程池ForkJoinPool 要合理运用,不是什么样的任务都拿来用
- MySQL隔离级别解析:数据一致性与高并发之间的平衡术!
- 了解Android系统架构中的HAL硬件抽象层