为何两种Tuple类模板实现内存占用不同?能否修复至相同大小?
可变参数Tuple模板与特化版本的内存差异问题
我实现了两种Tuple类模板——一种是针对不同参数数量的特化版本,另一种用可变参数模板实现。当把空类Nil作为元组的部分元素时,两种实现的内存占用不一样。为什么可变参数模板的第二种实现内存更大?能不能把它修复到和第一种一样的大小?
代码实现
#include <iostream> using namespace std; struct Nil {}; template <typename T1 = Nil, typename T2 = Nil> struct Tuple1 : Tuple1<T2> { T1 x; using Base = Tuple1<T2>; Base* base() { return static_cast<Base*>(this); } const Base* base() const { return static_cast<const Base*>(this); } Tuple1(const T1& t1, const T2& t2) : Base{ t2 }, x{ t1 } {} }; template <> struct Tuple1<> {}; template <typename T1> struct Tuple1<T1> : Tuple1<> { T1 x; using Base = Tuple1<>; Base* base() { return static_cast<Base*>(this); } const Base* base() const { return static_cast<const Base*>(this); } Tuple1(const T1& t1) : Base{}, x{ t1 } {} }; // --------------------------------------------------------------------------- template <typename...> struct Tuple2; template <> struct Tuple2<> {}; template <typename Head, typename... Tail> struct Tuple2<Head, Tail...> : Tuple2<Tail...> { Tuple2(const Head& head, const Tail&... tail) : Base{ tail... }, m_head{ head } {} private: using Base = Tuple2<Tail...>; Head m_head; }; int main() { cout << "Tuple1 sizes:\n"; cout << sizeof(Tuple1<>) << '\n'; cout << sizeof(Tuple1<int*>) << '\n'; cout << sizeof(Tuple1<int*, Nil>) << '\n'; cout << '\n'; cout << "Tuple2 sizes:\n"; cout << sizeof(Tuple2<>) << '\n'; cout << sizeof(Tuple2<int*>) << '\n'; cout << sizeof(Tuple2<int*, Nil>) << '\n'; return 0; }
运行结果(MSVC 2022)
Tuple1 sizes: 1 8 8 Tuple2 sizes: 1 8 16
问题原因
- Tuple1的特化优化:
Tuple1<int*, Nil>基于特化模板实现,它继承的Tuple1<Nil>虽然包含一个Nil成员,但MSVC对这种仅含空类成员的基类做了内存合并优化——将基类的1字节占用合并到派生类int*成员的对齐填充空间中,最终整个类的大小等于int*的大小(8字节)。 - Tuple2的布局与对齐:
Tuple2<int*, Nil>的可变参数实现没有针对Nil做特殊处理,它继承的Tuple2<Nil>是一个包含Nil成员的类(大小1字节),再加上自身的int*成员(8字节)。由于内存对齐要求,Tuple2<Nil>的1字节会被填充到8字节,再加上int*的8字节,总大小被对齐到16字节。
修复方法
为Tuple2添加针对首参数为Nil的特化版本,当模板参数中出现Nil时,跳过该成员的存储,直接继承后续参数的Tuple2实例:
// 添加针对首参数为Nil的特化模板 template <typename... Tail> struct Tuple2<Nil, Tail...> : Tuple2<Tail...> { Tuple2(const Nil&, const Tail&... tail) : Tuple2<Tail...>(tail...) {} };
修改后,Tuple2<int*, Nil>会直接继承Tuple2<int*>,而Tuple2<int*>的大小为8字节,因此Tuple2<int*, Nil>的大小也会变为8字节,与Tuple1<int*, Nil>一致。如果需要处理多个连续Nil的情况,可以继续添加更多特化,或者使用递归模板逻辑自动跳过所有Nil参数。
内容的提问来源于stack exchange,提问作者bobeff
相关产品推荐
相关产品推荐

