很久以前,为了提高现有项目的可读性,某些定义被枚举替换,并且枚举通过重载运算符进行了“增强”,以便能够组合枚举。
例如:
enum Props
{
Prop_1 = 0x00000001,
Prop_2 = 0x00000002,
Prop_3 = 0x00000004,
Prop_4 = 0x00000100,
Prop_Group1 = 0x000000FF,
} ;
Props Properties = Prop_1 ; // Initial
Properties &= Prop_2 ; // For some reason we add another property
在我的搜索中,建议我使用以下宏
#define MY_DEFINE_ENUM_FLAG_OPERATORS(ENUMTYPE) \
inline ENUMTYPE operator | (ENUMTYPE a, ENUMTYPE b) { return ENUMTYPE(((DWORD)a) | ((DWORD)b)); } \
inline ENUMTYPE &operator |= (ENUMTYPE &a, ENUMTYPE b) { return (ENUMTYPE &)(((DWORD &)a) |= ((DWORD)b)); } \
inline ENUMTYPE operator & (ENUMTYPE a, ENUMTYPE b) { return ENUMTYPE(((DWORD)a) & ((DWORD)b)); } \
inline ENUMTYPE &operator &= (ENUMTYPE &a, ENUMTYPE b) { return (ENUMTYPE &)(((DWORD &)a) &= ((DWORD)b)); } \
inline ENUMTYPE operator ~ (ENUMTYPE a) { return ENUMTYPE(~((DWORD)a)); } \
inline ENUMTYPE operator ^ (ENUMTYPE a, ENUMTYPE b) { return ENUMTYPE(((DWORD)a) ^ ((DWORD)b)); } \
inline ENUMTYPE &operator ^= (ENUMTYPE &a, ENUMTYPE b) { return (ENUMTYPE &)(((DWORD &)a) ^= ((DWORD)b)); } \
所以,通过在标头中应用宏:
MY_DEFINE_ENUM_FLAG_OPERATORS(Props)
这实际上在 Borland / CodeGear 和 Embarcadero C++ Builder 的许多版本中一直运行良好,所以我再也没有看过它
今天我正在处理一种可以从此功能中受益的情况,并且枚举有许多可能的状态,超过 32 个,所以我想我将宏更改为 64 位变量而不是 32 位变量(一个宏适合所有情况)
像这样:
typedef unsigned __int64 UI64 ;
#define MY_DEFINE_ENUM_FLAG_OPERATORS(ENUMTYPE) \
inline ENUMTYPE operator | (ENUMTYPE a, ENUMTYPE b) { return ENUMTYPE(((UI64)a) | ((UI64)b)); } \
inline ENUMTYPE &operator |= (ENUMTYPE &a, ENUMTYPE b) { return (ENUMTYPE &)(((UI64 &)a) |= ((UI64)b)); } \
inline ENUMTYPE operator & (ENUMTYPE a, ENUMTYPE b) { return ENUMTYPE(((UI64)a) & ((UI64)b)); } \
inline ENUMTYPE &operator &= (ENUMTYPE &a, ENUMTYPE b) { return (ENUMTYPE &)(((UI64 &)a) &= ((UI64)b)); } \
inline ENUMTYPE operator ~ (ENUMTYPE a) { return ENUMTYPE(~((UI64)a)); } \
inline ENUMTYPE operator ^ (ENUMTYPE a, ENUMTYPE b) { return ENUMTYPE(((UI64)a) ^ ((UI64)b)); } \
inline ENUMTYPE &operator ^= (ENUMTYPE &a, ENUMTYPE b) { return (ENUMTYPE &)(((UI64 &)a) ^= ((UI64)b)); } \
这是一个错误,因为代码由于变量损坏而开始崩溃,再加上旧枚举的可能值要少得多
在以下情况下注意到(示例)
void Function (void *Input1, Props Properties, bool Whatever)
{
Properties &= Prop_Group1 ; // Corrupts Input1
// ..
}
我注意到Input1被设置为NULL而不是传递给函数的值
我现在意识到宏观从来就不是100%安全的! 特别是
&=
、|=
和^=
所有枚举(使用 MACRO)在堆栈上必须为 4 个字节,否则可能会出现问题(使用旧的 DWORD 转换 MACRO)。实际上可能就是这样,因为我倾向于写 =
0x00000000
。
确实是时候回顾一下了
因此,当我更改为 64 位类型 MACRO 时,迄今为止使用的所有枚举都占用了堆栈上的 4 个字节,从而导致了问题。
我觉得宏观可以变得更安全,但我希望你能提供意见,以确保 10 年后我不会再遇到另一个“啊哈”(或“哦操”)时刻
这就是我最终根据我的问题中发布的评论“解决”问题的方式(对此我表示感谢)。
PS。我目前使用两个编译器版本,一个支持 c++ 11,一个支持 c++ 17。
#ifdef
反映了这一点,如果您使用位于中间的编译器,则必须进行调整。就我而言,任何进入“世界”的东西都是使用最新版本构建的,所以我不太担心旧编译器。
// https://docwiki.embarcadero.com/RADStudio/Alexandria/en/Predefined_Macros#C.2B.2B_Compiler_Versions_in_Predefined_Macros
// https://stackoverflow.com/questions/77695203/overloaded-macro-to-add-functionality-to-enum-corrupts-the-stack
#if defined(__BCPLUSPLUS__) && (__BCPLUSPLUS__ >= 0x0760) // >= C++ Builder 11
// One MACRO fits all
#define MY_DEFINE_ENUM_FLAG_OPERATORS(ENUMTYPE) \
inline ENUMTYPE operator | (ENUMTYPE a, ENUMTYPE b) { return ENUMTYPE(((std::underlying_type_t<ENUMTYPE>)a) | ((std::underlying_type_t<ENUMTYPE>)b)); } \
inline ENUMTYPE &operator |= (ENUMTYPE &a, ENUMTYPE b) { return (ENUMTYPE &)(((std::underlying_type_t<ENUMTYPE> &)a) |= ((std::underlying_type_t<ENUMTYPE>)b)); } \
inline ENUMTYPE operator & (ENUMTYPE a, ENUMTYPE b) { return ENUMTYPE(((std::underlying_type_t<ENUMTYPE>)a) & ((std::underlying_type_t<ENUMTYPE>)b)); } \
inline ENUMTYPE &operator &= (ENUMTYPE &a, ENUMTYPE b) { return (ENUMTYPE &)(((std::underlying_type_t<ENUMTYPE> &)a) &= ((std::underlying_type_t<ENUMTYPE>)b)); } \
inline ENUMTYPE operator ~ (ENUMTYPE a) { return ENUMTYPE(~((std::underlying_type_t<ENUMTYPE>)a)); } \
inline ENUMTYPE operator ^ (ENUMTYPE a, ENUMTYPE b) { return ENUMTYPE(((std::underlying_type_t<ENUMTYPE>)a) ^ ((std::underlying_type_t<ENUMTYPE>)b)); } \
inline ENUMTYPE &operator ^= (ENUMTYPE &a, ENUMTYPE b) { return (ENUMTYPE &)(((std::underlying_type_t<ENUMTYPE> &)a) ^= ((std::underlying_type_t<ENUMTYPE>)b)); } \
#define MY_DEFINE_ENUM_FLAG_OPERATORS_DWORD MY_DEFINE_ENUM_FLAG_OPERATORS
#define MY_DEFINE_ENUM_FLAG_OPERATORS_UI64 MY_DEFINE_ENUM_FLAG_OPERATORS
#else
// One MACRO fits all
// Use tmp value for &=, |= and ^=
// Enums cannot exceed 8 bytes for this MACRO to work !
#define MY_DEFINE_ENUM_FLAG_OPERATORS_GEN(ENUMTYPE) \
inline ENUMTYPE operator | (ENUMTYPE a, ENUMTYPE b) { return ENUMTYPE(((UI64)a) | ((UI64)b)); } \
inline ENUMTYPE &operator |= (ENUMTYPE &a, ENUMTYPE b) { a = ENUMTYPE((UI64)a | (UI64)b); return a; } \
inline ENUMTYPE operator & (ENUMTYPE a, ENUMTYPE b) { return ENUMTYPE(((UI64)a) & ((UI64)b)); } \
inline ENUMTYPE &operator &= (ENUMTYPE &a, ENUMTYPE b) { a = ENUMTYPE((UI64)a & (UI64)b); return a; } \
inline ENUMTYPE operator ~ (ENUMTYPE a) { return ENUMTYPE(~((UI64)a)); } \
inline ENUMTYPE operator ^ (ENUMTYPE a, ENUMTYPE b) { return ENUMTYPE(((UI64)a) ^ ((UI64)b)); } \
inline ENUMTYPE &operator ^= (ENUMTYPE &a, ENUMTYPE b) { a = ENUMTYPE((UI64)a ^ (UI64)b); return a; } \
// Enums *must* be 4 bytes for following MACRO to work !
// Otherwise the MACRO can corrupt the stack
// Make sure no values are bigger than 0xFFFFFFFF
// Optionally use syntax: typedef enum: DWORD { } Name ;
#define MY_DEFINE_ENUM_FLAG_OPERATORS_DWORD(ENUMTYPE) \
inline ENUMTYPE operator | (ENUMTYPE a, ENUMTYPE b) { return ENUMTYPE(((DWORD)a) | ((DWORD)b)); } \
inline ENUMTYPE &operator |= (ENUMTYPE &a, ENUMTYPE b) { return (ENUMTYPE &)(((DWORD &)a) |= ((DWORD)b)); } \
inline ENUMTYPE operator & (ENUMTYPE a, ENUMTYPE b) { return ENUMTYPE(((DWORD)a) & ((DWORD)b)); } \
inline ENUMTYPE &operator &= (ENUMTYPE &a, ENUMTYPE b) { return (ENUMTYPE &)(((DWORD &)a) &= ((DWORD)b)); } \
inline ENUMTYPE operator ~ (ENUMTYPE a) { return ENUMTYPE(~((DWORD)a)); } \
inline ENUMTYPE operator ^ (ENUMTYPE a, ENUMTYPE b) { return ENUMTYPE(((DWORD)a) ^ ((DWORD)b)); } \
inline ENUMTYPE &operator ^= (ENUMTYPE &a, ENUMTYPE b) { return (ENUMTYPE &)(((DWORD &)a) ^= ((DWORD)b)); } \
// Enums *must* be 8 bytes for following MACRO to work !
// Otherwise the MACRO can corrupt the stack
// Best make sure there is a value higher than 0xFFFFFFFF
// Optionally use syntax: typedef enum: UI64 { } Name ;
#define MY_DEFINE_ENUM_FLAG_OPERATORS_UI64(ENUMTYPE) \
inline ENUMTYPE operator | (ENUMTYPE a, ENUMTYPE b) { return ENUMTYPE(((UI64)a) | ((UI64)b)); } \
inline ENUMTYPE &operator |= (ENUMTYPE &a, ENUMTYPE b) { return (ENUMTYPE &)(((UI64 &)a) |= ((UI64)b)); } \
inline ENUMTYPE operator & (ENUMTYPE a, ENUMTYPE b) { return ENUMTYPE(((UI64)a) & ((UI64)b)); } \
inline ENUMTYPE &operator &= (ENUMTYPE &a, ENUMTYPE b) { return (ENUMTYPE &)(((UI64 &)a) &= ((UI64)b)); } \
inline ENUMTYPE operator ~ (ENUMTYPE a) { return ENUMTYPE(~((UI64)a)); } \
inline ENUMTYPE operator ^ (ENUMTYPE a, ENUMTYPE b) { return ENUMTYPE(((UI64)a) ^ ((UI64)b)); } \
inline ENUMTYPE &operator ^= (ENUMTYPE &a, ENUMTYPE b) { return (ENUMTYPE &)(((UI64 &)a) ^= ((UI64)b)); } \
// Default to the DWORD version
#define MY_DEFINE_ENUM_FLAG_OPERATORS MY_DEFINE_ENUM_FLAG_OPERATORS_DWORD
#endif