文章目录
- [第一章 C++20核心语法特性](#第一章 C++20核心语法特性)
-
- [1.10 [[no_unique_address]]属性](#1.10 [[no_unique_address]]属性)
-
- [1.10.1 编译器优化原理](#1.10.1 编译器优化原理)
- [1.10.2 举例说明](#1.10.2 举例说明)
本文记录C++20新特性之[[no_unique_address]]属性。
第一章 C++20核心语法特性
1.10 [[no_unique_address]]属性
在 C++ 标准中,为了保证每个对象都有唯一的内存地址(以便指针区分),即使是一个空类(Empty Class,没有任何数据成员,只有函数),其实例化后的大小也至少是 1 字节。
cpp
struct Empty {}; // 大小为 1 字节
struct Wrapper {
int id;
Empty e; // 占用 1 字节,加上内存对齐,Wrapper 8 字节
};
void test()
{
cout << sizeof(Empty) << endl;
// 1
cout << sizeof(Wrapper) << endl;
// 8
}
在泛型编程中,我们经常需要将一些无状态的策略类(Policy Classes,如分配器 Allocator、比较器 Comparator)作为成员变量存储。如果这些类是空的,它们占用的这 1 字节(加上对齐填充)就是纯粹的内存浪费。
在 C++20 之前,为了消除这 1 字节的开销,不得不使用复杂的 EBO (空基类优化) 技巧,强行让 Wrapper 继承 Empty,而不是包含它。这导致代码结构变得扭曲且难以阅读。
EBO优化如下:
cpp
struct Empty {}; // 大小为 1 字节
struct Wrapper {
int id;
Empty e; // 占用 1 字节,加上内存对齐,Wrapper 可能变成 8 字节
};
// 使用 EBO 的版本
struct WrapperEBO : private Empty { // 通过继承来应用 EBO
int id;
};
void test()
{
std::cout << "sizeof(Empty): " << sizeof(Empty) << std::endl;
// 输出: 1
std::cout << "sizeof(Wrapper) [未优化]: " << sizeof(Wrapper) << std::endl;
// 输出: 8 (在典型的64位系统上,4字节int + 1字节Empty + 3字节对齐填充)
std::cout << "sizeof(WrapperEBO) [EBO 优化]: " << sizeof(WrapperEBO) << std::endl;
// 输出: 4 (Empty 基类的大小被优化掉了)
}
C++20 引入了一个新的属性 [[no_unique_address]],它打破了"每个成员变量必须占用独立地址"的限制,为类成员的内存布局优化带来了革命性的变化。
1.10.1 编译器优化原理
\[no_unique_address\]\] 是一个属性(Attribute),用于修饰类的非静态数据成员。
作用:告诉编译器,被\[no_unique_address\] 修饰的变量不需要拥有唯一的内存地址。
效果:
如果该成员是空类:编译器可以将其大小优化为 0。它在内存中不占用任何空间,就像它不存在一样。
如果该成员非空:编译器可以将其与其他成员重叠布局(虽然这种情况较少见,主要取决于编译器的具体实现),或者仅仅是像往常一样存储。
限制:如果同一个类中有两个相同类型的成员都标记了此属性,它们不能共享同一个地址(必须能区分彼此)。
#### 1.10.2 举例说明
示例1:消除空成员的开销。
```cpp
struct Empty {}; // 空类
struct WithoutAttr {
int i;
Empty e;
};
struct WithAttr {
int i;
[[no_unique_address]] Empty e; // 开启优化
};
void test()
{
cout << sizeof(Empty) << endl;
// 1
cout << sizeof(WithoutAttr) << endl;
//8 ,(4字节int + 1字节Empty + 3字节padding)
cout << sizeof(WithAttr) << endl;
//8,(VS2026输出8,理想状态:Empty被优化为0字节,输出 4)
}
```
示例2:优化自定义分配器或哈希函数
在编写容器或工具类时,我们经常需要持有一个分配器或哈希函数对象。大多数默认的分配器(如 std::allocator)都是无状态的空类,此时就可以将 Alloc 加上\[\[no_unique_address\]\]修饰。
```cpp
template