C++20新特性_[[no_unique_address]]属性

文章目录

  • [第一章 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 <typename T, typename Alloc = std::allocator<T>>
class MyVector {
    T* data;
    size_t size;
    
    // C++20 之前:为了省空间,可能需要私有继承 Alloc
    // C++20:直接作为成员,加上属性即可
    [[no_unique_address]] Alloc allocator; 

public:
    // ...
};
相关推荐
楼田莉子2 天前
C++20新特性:协程
开发语言·c++·后端·学习·c++20
ouliten4 天前
C++笔记:C++20风格线程池
c++·笔记·c++20
眠りたいです6 天前
现代C++:C++17中的新库特性
开发语言·c++·c++20·c++17
楼田莉子12 天前
C++20新特性:Range库
开发语言·c++·后端·学习·c++20
楼田莉子13 天前
C++20现代特性:概念与约束
开发语言·c++·后端·学习·c++20
aluluka13 天前
C++ 20 协程的探索
c++·c++20
君鼎17 天前
内存池完整实现——C++20版
c++20·内存池
普通网友1 个月前
记录我适配iOS26遇到的一些问题
c++20
前进吧-程序员1 个月前
C++20/23 Ranges:从「迭代器对」到「可组合管道」
c++20
Shan12051 个月前
实例分析:C++20的std::jthread
c++20