如何让宏在头文件与源文件中实现差异化展开?
问题描述
我现在用的是下面这套智能枚举结构:
/* MySmartEnum.hpp */ #pragma once #include <array> template <class E> static const auto& value_string_pairs(); #define MY_EXPAND(x) x #define MY_ARRAY_PAIR(name, x) { #x, name::x } #define MY_STR_CONCAT_(a, ...) a##__VA_ARGS__ #define MY_STR_CONCAT(a, ...) MY_STR_CONCAT_(a, __VA_ARGS__) #define MY_ARG_COUNT_(_1,_2,_3,_4,VAL,...) VAL #define MY_ARG_COUNT(...) MY_EXPAND(MY_ARG_COUNT_(__VA_ARGS__,4,3,2,1)) #define MY_LOOP_1(f, n, x) f(n,x) #define MY_LOOP_2(f, n, x, ...) f(n,x) , MY_EXPAND(MY_LOOP_1(f, n, __VA_ARGS__)) #define MY_LOOP_3(f, n, x, ...) f(n,x) , MY_EXPAND(MY_LOOP_2(f, n, __VA_ARGS__)) #define MY_ENUM_IMPL(name, loop_func_name, ...) \ template<> inline const auto& value_string_pairs<name>() { \ using element_t = std::pair<const char*, name>; \ static const std::array<element_t, MY_ARG_COUNT(__VA_ARGS__)> values = { element_t \ MY_EXPAND(loop_func_name(MY_ARRAY_PAIR, name, __VA_ARGS__)) }; \ return values; } #define MY_ENUM_CLASS(name, ...) enum class name { __VA_ARGS__ }; \ MY_ENUM_IMPL(name, MY_STR_CONCAT(MY_LOOP_, MY_ARG_COUNT(__VA_ARGS__)), __VA_ARGS__)
还有一个管理所有枚举的头文件:
/* Enums.hpp */ #pragma once #include "MySmartEnum.hpp" MY_ENUM_CLASS(Timespan, Week, Month, Year) MY_ENUM_CLASS(Interpolation, Linear, Cubic)
比如MY_ENUM_CLASS(Timespan, Week, Month, Year)会展开成:
enum class Timespan { Week, Month, Year }; template<> inline const auto& value_string_pairs<Timespan>() { using element_t = std::pair<const char*, Timespan>; static const std::array<element_t, 3> values = { element_t { "Week", Timespan::Week }, { "Month", Timespan::Month }, { "Year", Timespan::Year } }; return values; }
现在所有实现都在头文件里,我想把value_string_pairs的实现移到Enums.cpp里。已经把头文件里的MY_ENUM_CLASS改成只定义枚举类和函数声明了:
#define MY_ENUM_CLASS(name, ...) enum class name { __VA_ARGS__ }; \ template<> const auto& value_string_pairs<name>();
但怎么把value_string_pairs<Timespan>()的实现移到Enums.cpp,还不用重复写所有枚举值?能不能在Enums.cpp里先包含Enums.hpp,然后取消并重新定义MY_ENUM_CLASS,让它包含MY_ENUM_IMPL,这样头文件里的MY_ENUM_CLASS调用在源文件里就能展开成完整的函数实现?
解决方案
完全可以这么做,核心思路是利用宏的重定义特性,让同一个MY_ENUM_CLASS调用在头文件和源文件中展开成不同内容:
步骤1:保持头文件的调整结果
- 继续保留
MySmartEnum.hpp里所有辅助宏(MY_EXPAND、MY_ARRAY_PAIR等)和MY_ENUM_IMPL的定义,只让MY_ENUM_CLASS负责声明枚举类和函数签名:
// MySmartEnum.hpp 中调整后的MY_ENUM_CLASS #define MY_ENUM_CLASS(name, ...) enum class name { __VA_ARGS__ }; \ template<> const auto& value_string_pairs<name>();
Enums.hpp内容不变,依然用MY_ENUM_CLASS声明各个枚举。
步骤2:编写Enums.cpp实现文件
在Enums.cpp中先取消原MY_ENUM_CLASS宏,重新定义成带实现的版本,再包含Enums.hpp,这样头文件里的MY_ENUM_CLASS调用会自动展开为value_string_pairs的具体实现:
/* Enums.cpp */ #include "MySmartEnum.hpp" // 取消头文件中定义的MY_ENUM_CLASS宏 #undef MY_ENUM_CLASS // 重新定义MY_ENUM_CLASS,让它生成枚举类+value_string_pairs的实现 #define MY_ENUM_CLASS(name, ...) enum class name { __VA_ARGS__ }; \ MY_ENUM_IMPL(name, MY_STR_CONCAT(MY_LOOP_, MY_ARG_COUNT(__VA_ARGS__)), __VA_ARGS__) // 包含枚举头文件,此时MY_ENUM_CLASS会展开为带实现的版本 #include "Enums.hpp"
这里不需要重复枚举值,Enums.hpp里的MY_ENUM_CLASS调用会通过新宏自动生成value_string_pairs的模板特化实现。
关键细节说明
- 宏作用域仅限当前编译单元:源文件中的宏重定义只会影响当前
.cpp的编译,不会干扰头文件在其他编译单元的展开。 - 避免多重定义:把
value_string_pairs的模板特化放在.cpp里,不用加inline,因为实现只存在于一个源文件中,不会出现重复定义问题。 - 复用现有宏代码:
MySmartEnum.hpp里的MY_ENUM_IMPL、MY_LOOP_*等辅助宏可以直接复用,不用重复编写,减少冗余。
内容的提问来源于stack exchange,提问作者Phil-ZXX
相关产品推荐
相关产品推荐

