我们即将(大约在接下来的两年内)将我们所有的编译器迁移到 C++11 就绪的编译器。
我们的客户将使用我们的 header ,而我们现在可以为我们的新 API 编写(或多或少从头开始) header 。
因此我们必须在保留 C++03 枚举(及其所有缺陷)和使用包装类来模拟 C++11 符号之间做出选择,因为我们最终希望将这些枚举移至 C++ 11.
下面提出的“LikeEnum”成语是可行的解决方案,还是背后隐藏着意想不到的惊喜?
template<typename def, typename inner = typename def::type>
class like_enum : public def
{
typedef inner type;
inner val;
public:
like_enum() {}
like_enum(type v) : val(v) {}
operator type () const { return val; }
friend bool operator == (const like_enum & lhs, const like_enum & rhs) { return lhs.val == rhs.val; }
friend bool operator != (const like_enum & lhs, const like_enum & rhs) { return lhs.val != rhs.val; }
friend bool operator < (const like_enum & lhs, const like_enum & rhs) { return lhs.val < rhs.val; }
friend bool operator <= (const like_enum & lhs, const like_enum & rhs) { return lhs.val <= rhs.val; }
friend bool operator > (const like_enum & lhs, const like_enum & rhs) { return lhs.val > rhs.val; }
friend bool operator >= (const like_enum & lhs, const like_enum & rhs) { return lhs.val >= rhs.val; }
};
这将使我们能够升级我们的枚举,而无需对用户代码进行不必要的更改:
// our code (C++03) | our code C++11
// --------------------------------------+---------------------------
|
struct KlingonType | enum class Klingon
{ | {
enum type | Qapla,
{ | Ghobe,
Qapla, | Highos
Ghobe, | } ;
Highos |
} ; |
} ; |
|
typedef like_enum<KlingonType> Klingon ; |
|
// --------------------------------------+---------------------------
// client code (both C++03 and C++11)
void foo(Klingon e)
{
switch(e)
{
case Klingon::Qapla : /* etc. */ ; break ;
default : /* etc. */ ; break ;
}
}
注意:LikeEnum 的灵感来自 Type Safe Enum idiom
注意 2:源兼容性不包括编译错误,因为隐式转换为 int:那些被认为是不可取的,并且会提前通知客户端进行显式到整数的转换。
最佳答案
简短的回答是肯定的,这是一个可行的解决方案(只需修复一次)。
这是长答案。 :)
严格来说,您的比较函数存在编译时错误。这将导致与标准兼容的编译器的可移植性问题。特别要考虑以下几点:
bool foo(Klingon e) { return e == Klingon::Qapla }
编译器不应该知道 operator==
的哪个重载使用,同时转换 e
至 KlingonType::type
隐式地(通过 operator type() const
)并转换 Klingon::Qapla
至 Klingon
隐式地(通过 Klingon(type)
)需要一次转换。
需要 operator type() const
成为explicit
将修复此错误。当然,explicit
在 C++03 中不存在。这意味着您必须按照@Yakk 在评论中的建议进行操作,并使用类似于 inner
的 safe-bool 习语的东西。的类型。删除 operator type() const
完全不是一个选项,因为它会删除对整数类型的显式转换。
既然你说隐式转换仍然是可能的,那么一个更简单的解决方法是同时定义与底层 enum
的比较函数。类型。所以除了:
friend bool operator == (const like_enum & lhs, const like_enum & rhs) { return lhs.val == rhs.val; }
friend bool operator != (const like_enum & lhs, const like_enum & rhs) { return lhs.val != rhs.val; }
friend bool operator < (const like_enum & lhs, const like_enum & rhs) { return lhs.val < rhs.val; }
friend bool operator <= (const like_enum & lhs, const like_enum & rhs) { return lhs.val <= rhs.val; }
friend bool operator > (const like_enum & lhs, const like_enum & rhs) { return lhs.val > rhs.val; }
friend bool operator >= (const like_enum & lhs, const like_enum & rhs) { return lhs.val >= rhs.val; }
您还需要:
friend bool operator ==(const like_enum& lhs, const type rhs) { return lhs.val == rhs; }
friend bool operator !=(const like_enum& lhs, const type rhs) { return lhs.val != rhs; }
friend bool operator < (const like_enum& lhs, const type rhs) { return lhs.val < rhs; }
friend bool operator <=(const like_enum& lhs, const type rhs) { return lhs.val <= rhs; }
friend bool operator > (const like_enum& lhs, const type rhs) { return lhs.val > rhs; }
friend bool operator >=(const like_enum& lhs, const type rhs) { return lhs.val >= rhs; }
friend bool operator ==(const type lhs, const like_enum& rhs) { return operator==(rhs, lhs); }
friend bool operator !=(const type lhs, const like_enum& rhs) { return operator!=(rhs, lhs); }
friend bool operator < (const type lhs, const like_enum& rhs) { return operator> (rhs, lhs); }
friend bool operator <=(const type lhs, const like_enum& rhs) { return operator>=(rhs, lhs); }
friend bool operator > (const type lhs, const like_enum& rhs) { return operator< (rhs, lhs); }
friend bool operator >=(const type lhs, const like_enum& rhs) { return operator<=(rhs, lhs); }
修复上述问题后,语义上几乎没有明显差异(忽略可能的隐式转换)。我发现的唯一区别是 std::is_pod<Klingon>::value
的值来自 <type_traits>
在 C++11 中。使用 C++03 版本,这将是 false
,而使用 enum class
es,这将是true
.实际上,这意味着(没有优化)Klingon
使用 enum class
可以在寄存器中随身携带,而 like_enum
版本将需要在堆栈上。
因为您没有指定 enum class
的底层表示, sizeof(Klingon)
两者可能相同,但我不会依赖它。不同实现选择的底层表示的不可靠性是强类型 enum
背后的部分动机。毕竟。
Here's proof以上两段针对 clang++ 3.0+、g++ 4.5+ 和 msvc 11+。
现在就编译输出而言,两者显然都具有不兼容的 ABI。这意味着您的整个代码库需要使用其中之一。他们不会混合。对于我的系统(OSX 上的 clang++-3.5),上述函数的符号是 __Z1f9like_enumI11KlingonTypeNS0_4typeEE
对于 C++03 版本和 __Z1f7Klingon
对于 C++11 版本。如果这些是导出的库函数,这应该只是一个问题。
在将优化转为 -O2
之后,我对 clang++ 和 g++ 的测试输出的程序集是相同的 。大概其他优化编译器也将能够解包 Klingon
至 KlingonType::type
.如果不进行优化,enum class
当然,版本仍将避免所有构造函数和比较运算符函数调用。
关于c++ - 对于从 C++03 枚举到 C++11 枚举类的源向后兼容迁移,这种模式是否可行?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23270788/