C++20标准引入了多个新特性,其中
[[no_unique_address]]属性是一个旨在优化内存布局的重要工具,特别适用于减少空类对象的空间开销。本文将深入探讨这一属性的工作原理、使用场景和注意事项。
在C++中,即使是空类(没有任何非静态数据成员的类),其对象大小也 至少为1字节。这是因为C++标准要求同一类型的不同实例必须拥有不同的地址 。
struct Empty {};static_assert(sizeof(Empty) >= 1); // 总是成立当空类作为其他类的成员时,会导致内存浪费:
struct X {
int i;
Empty e; // 通常需要至少1字节,可能还有填充字节};static_assert(sizeof(X) >= sizeof(int) + 1); // 通常成立在C++20之前,解决空类空间开销的主要方法是使用 空基类优化(Empty Base Optimization, EBO):通过继承而不是组合来包含空类 。
struct Y : Empty { // 通过继承而非包含
int i;};// 由于空基类优化,sizeof(Y)可能等于sizeof(int))但EBO有局限性:不能用于多个相同类型的空类(因为基类不能重复),且可能破坏类的设计意图("Is-a" vs "Has-a"关系) 。
[[no_unique_address]]的语法与语义C++20引入了
[[no_unique_address]]属性,允许类的非静态数据成员(非位域)与其他成员或基类子对象共享地址
。
struct Y {
int i;
[[no_unique_address]] Empty e; // 使用新属性};static_assert(sizeof(Y) == sizeof(int)); // 在许多平台上成立容器通常需要存储分配器实例,即使是无状态的分配器也会占用空间。
[[no_unique_address]]可以优化这种情况
。
template<typename T, typename Allocator = std::allocator<T>>class Vector {private:
T* data_;https://zq.zhaopin.com/moment/71183234
size_t size_;
size_t capacity_;
[[no_unique_address]] Allocator allocator_; // 无状态分配器可能不占空间public:
// ... 接口实现};
std::unique_ptr的删除器、比较函数等可以使用此属性优化
。
template<typename T, typename Deleter = std::default_delete<T>>class unique_ptr {
T* ptr;https://zq.zhaopin.com/moment/71183260
[[no_unique_address]] Deleter d; // 如果Deleter是空类,则可能不占空间public:
~unique_ptr() { d(ptr); }};当类有多个空类型成员时,即使使用
[[no_unique_address]],
相同类型的成员仍不能共享地址(因为C++对象身份规则),但它们可以与不同类型成员共享地址
。
struct Z {https://zq.zhaopin.com/moment/71183276
char c;
[[no_unique_address]] Empty e1, e2; // e1和e2不能共享相同地址};static_assert(sizeof(Z) >= 2); // 因为e1和e2类型相同,需要不同地址struct W {
char c[2];
[[no_unique_address]] Empty e1, e2;};// e1可能与c[0]共享地址,e2可能与c[1]共享地址std::cout << "sizeof(W) == 2 is " << (sizeof(W) == 2) << '\n'; // 可能是truehttps://zq.zhaopin.com/moment/71183284
相同类型的限制:相同类型的多个
[[no_unique_address]]成员仍需要不同地址,这是由C++对象模型的基本规则决定的
。
编译器支持:需要注意的是,即使在C++20模式下,MSVC也会忽略
[[no_unique_address]],而是提供了
[[msvc::no_unique_address]]作为替代
。
不是零大小:
[[no_unique_address]]并不创建零大小对象(C++中不存在零大小对象),而是允许编译器优化掉空成员的占用空间
。
类型特性:可使用
std::is_empty_v<T>检查类型是否为空,帮助决定是否使用此属性。
| 特性 | 继承(EBO) |
[[no_unique_address]] |
|---|---|---|
| 支持多个相同类型空对象 | ❌ | ⚠️(有限制) |
| 保持"Has-a"关系 | ❌ | ✅ |
可用于
final类 |
❌ | ✅ |
| 避免成员名称隐藏 | ✅ | ❌ |
优先用于无状态对象:如分配器、比较器、删除器等。
考虑ABI兼容性:在公开接口中需谨慎使用,因为布局可能影响二进制兼容性。
配合类型特征使用:
template<typename T, typename U>struct Pair {https://zq.zhaopin.com/moment/71183292
T first;
[[no_unique_address]] std::conditional_t<std::is_empty_v<U>,
EmptyWrapper<U>, U> second;};测试实际效果:不同编译器可能有不同优化行为,需实际测试内存布局。
[[no_unique_address]]是C++20中一个实用的属性,它解决了长期存在的空对象空间开销问题,提供了比传统空基类优化更直观和安全的选择。虽然在使用上仍有一些限制(特别是对相同类型成员的处理),但在大多数情况下,它能有效优化内存使用,特别是在泛型编程和模板库实现中。
随着编译器对C++20支持的不断完善,
[[no_unique_address]]将成为C++开发者工具箱中的重要工具,用于编写更高效、更简洁的代码。
进一步阅读:C++20还引入了
[[likely]]和[[unlikely]]属性用于优化分支预测 ,以及其他一些有用特性,值得探索。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69960915/viewspace-3101105/ ,如需转载,请注明出处,否则将追究法律责任。