如何在不增加类大小的前提下兼容构造初始化列表的条件宏?
问题描述
我有一套用于条件生成和初始化类成员的宏,代码如下:
#ifdef LIFETIME_TRACKING #define DEFINE_TRACKER_MEMBER() mutable ::LifetimeTracker m_tracker #define INITIALIZE_TRACKER_MEMBER(x) m_tracker(x) #else #define DEFINE_TRACKER_MEMBER() using dummy = void #define INITIALIZE_TRACKER_MEMBER(x) /* ??? */ #endif
宏的使用方式示例:
struct Example { int data; Dependency dep; DEFINE_TRACKER_MEMBER(); Example() : data(42), INITIALIZE_TRACKER_MEMBER(dep) { } };
当定义LIFETIME_TRACKING时一切正常,但未定义该宏时,我找不到既能让构造函数初始化列表里的, INITIALIZE_TRACKER_MEMBER(dep)语法通过编译,又不额外增大类体积、不破坏语法美观(比如把逗号放进宏里)的方法。
我曾尝试让DEFINE_TRACKER_MEMBER()创建一个空结构体成员:
#define DEFINE_TRACKER_MEMBER() struct { } m_tracker #define INITIALIZE_TRACKER_MEMBER m_tracker{}
但这样会增加父类的体积,因此寻求更好的解决方案。
解决方案
方案1:C++20及以后(推荐)
利用C++20引入的[[no_unique_address]]属性,确保空成员不占用类的实例空间,同时保持原有语法完全不变:
#ifdef LIFETIME_TRACKING #define DEFINE_TRACKER_MEMBER() mutable ::LifetimeTracker m_tracker #define INITIALIZE_TRACKER_MEMBER(x) m_tracker(x) #else // 用[[no_unique_address]]修饰空结构体成员,编译器会优化掉其存储空间 #define DEFINE_TRACKER_MEMBER() [[no_unique_address]] struct {} m_tracker #define INITIALIZE_TRACKER_MEMBER(x) m_tracker() #endif
[[no_unique_address]]是标准属性,编译器会保证空成员不占用类实例的额外空间,完全符合需求。- 原有类定义和构造函数的语法无需任何修改。
方案2:兼容C++17及更早版本
如果需要兼容旧标准,可以利用空基类优化(EBO),调整宏定义让类继承一个空基类,同时保持构造函数语法不变:
#ifdef LIFETIME_TRACKING // 定义跟踪成员,不修改类继承关系 #define TRACKED_STRUCT(struct_name) struct struct_name #define DEFINE_TRACKER_MEMBER() mutable ::LifetimeTracker m_tracker #define INITIALIZE_TRACKER_MEMBER(x) m_tracker(x) #else // 定义空基类,利用EBO不占用类空间 struct EmptyTracker {}; #define TRACKED_STRUCT(struct_name) struct struct_name : private EmptyTracker #define DEFINE_TRACKER_MEMBER() /* 无成员定义 */ #define INITIALIZE_TRACKER_MEMBER(x) EmptyTracker() #endif
使用时只需将原有的struct Example替换为TRACKED_STRUCT(Example):
TRACKED_STRUCT(Example) { int data; Dependency dep; DEFINE_TRACKER_MEMBER(); Example() : data(42), INITIALIZE_TRACKER_MEMBER(dep) { } };
- 空基类优化是C++标准允许的行为,所有主流编译器都支持,空基类不会增加类实例的体积。
- 仅需修改类的定义方式,构造函数的初始化列表语法保持不变。
方案3:依赖编译器扩展(不推荐)
部分编译器(如GCC、Clang)在C++17及更早版本中,会自动优化掉类中非第一个的空成员(即使没有[[no_unique_address]])。这种情况下可以直接使用你最初的尝试,但这是非标准行为,兼容性无法保证:
#ifdef LIFETIME_TRACKING #define DEFINE_TRACKER_MEMBER() mutable ::LifetimeTracker m_tracker #define INITIALIZE_TRACKER_MEMBER(x) m_tracker(x) #else #define DEFINE_TRACKER_MEMBER() struct { } m_tracker #define INITIALIZE_TRACKER_MEMBER(x) m_tracker() #endif
内容的提问来源于stack exchange,提问作者Vittorio Romeo
相关产品推荐
相关产品推荐

