能否将[[no_unique_address]]用于非POD类型?以std::array为例
当为std::size_t[]这类普通旧数据(POD)数组使用[[no_unique_address]]属性时,数据大小为0的模板特化结构体尺寸可仅等同于index成员的大小,无需为data数组占用内存。但将该属性用于std::array时却无法实现此效果——这大概率是因为[[no_unique_address]]无法直接处理复合对象,而std::array的标准实现早于该属性的引入,因此没做对应的优化。
现在需要在结构体中直接嵌入std::array(不使用指针、引用等间接方式),有没有办法让[[no_unique_address]]对std::array起到如同对POD数组的作用?
示例代码
#include <array> #include <iostream> using data_type = std::size_t; template<std::size_t _data_size> struct ExtendableIndexArray { std::size_t index; [[no_unique_address]] std::array<data_type, _data_size> data; }; template<std::size_t _data_size> struct ExtendableIndexPOD { std::size_t index; [[no_unique_address]] data_type data[_data_size]; }; int main() { std::cout << "Sizeof std::size_t = " << sizeof(std::size_t) << "\n\n"; std::cout << "Sizeof ExtendableIndexPOD<0> = " << sizeof(ExtendableIndexPOD<0>) << '\n'; std::cout << "Sizeof ExtendableIndexPOD<1> = " << sizeof(ExtendableIndexPOD<1>) << '\n'; std::cout << '\n'; std::cout << "Sizeof ExtendableIndexArray<0> = " << sizeof(ExtendableIndexArray<0>) << '\n'; std::cout << "Sizeof ExtendableIndexArray<1> = " << sizeof(ExtendableIndexArray<1>) << '\n'; }
运行输出
Sizeof std::size_t = 8 Sizeof ExtendableIndexPOD<0> = 8 Sizeof ExtendableIndexPOD<1> = 16 Sizeof ExtendableIndexArray<0> = 16 Sizeof ExtendableIndexArray<1> = 16
核心思路是给std::array做一层轻量包装,利用模板特化让空数组的包装器成为空类型,这样[[no_unique_address]]就能生效。
方法1:封装适配std::array的模板结构体
写一个模板结构体,对数组大小为0的情况做特化,让它成为空类型;非空情况则直接包含std::array:
#include <array> #include <iostream> using data_type = std::size_t; // 通用模板:包装std::array template<std::size_t N> struct ArrayWrapper { std::array<data_type, N> data; }; // 特化:空数组时使用空结构体 template<> struct ArrayWrapper<0> {}; template<std::size_t _data_size> struct ExtendableIndexArray { std::size_t index; [[no_unique_address]] ArrayWrapper<_data_size> data_wrapper; }; template<std::size_t _data_size> struct ExtendableIndexPOD { std::size_t index; [[no_unique_address]] data_type data[_data_size]; }; int main() { std::cout << "Sizeof std::size_t = " << sizeof(std::size_t) << "\n\n"; std::cout << "Sizeof ExtendableIndexPOD<0> = " << sizeof(ExtendableIndexPOD<0>) << '\n'; std::cout << "Sizeof ExtendableIndexPOD<1> = " << sizeof(ExtendableIndexPOD<1>) << '\n'; std::cout << '\n'; std::cout << "Sizeof ExtendableIndexArray<0> = " << sizeof(ExtendableIndexArray<0>) << '\n'; std::cout << "Sizeof ExtendableIndexArray<1> = " << sizeof(ExtendableIndexArray<1>) << '\n'; }
此时运行输出会和ExtendableIndexPOD一致:
Sizeof std::size_t = 8 Sizeof ExtendableIndexPOD<0> = 8 Sizeof ExtendableIndexPOD<1> = 16 Sizeof ExtendableIndexArray<0> = 8 Sizeof ExtendableIndexArray<1> = 16
方法2:利用空基类优化(EBO)
让结构体继承自ArrayWrapper,空基类优化允许编译器省略空基类的内存占用:
template<std::size_t _data_size> struct ExtendableIndexArray : private ArrayWrapper<_data_size> { std::size_t index; // 提供访问数组的接口 std::array<data_type, _data_size>& data() { return ArrayWrapper<_data_size>::data; } const std::array<data_type, _data_size>& data() const { return ArrayWrapper<_data_size>::data; } };
这种方式同样能实现空数组时结构体大小等于index的效果。
原理说明
[[no_unique_address]]的作用是允许编译器优化空类型成员的内存占用,但std::array<data_type, 0>本身是复合类型,标准允许它的大小不为0(通常为1字节以保证对象地址唯一)。我们通过包装器将空数组的情况转化为真正的空类型,就能让[[no_unique_address]]发挥作用。
内容的提问来源于stack exchange,提问作者Damir Tenishev

