对于从 C++03 枚举到 C++11 枚举类的源向后兼容迁移,此模式是否可以
Is this pattern ok for a source backward-compatible migration from C++03 enum into C++11 enum class?
我们即将(大约在未来两年内(将所有编译器迁移到C++11就绪的编译器。
我们的客户将使用我们的标头,我们现在可以为我们的新 API 编写(或多或少从头开始(标头。
因此,我们必须选择保留 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 的灵感来自类型安全枚举成语
注意 2:源兼容性不包括编译错误,因为隐式转换为 int:这些被认为是不可取的,并且将提前通知客户端进行显式到整数的转换。
简短的回答是肯定的,这是一个可行的解决方案(有一个修复(。
这是很长的答案。:)
严格来说,您的比较函数存在编译时错误。这将导致符合标准的编译器出现可移植性问题。具体而言,请考虑以下事项:
bool foo(Klingon e) { return e == Klingon::Qapla }
编译器不应该知道要使用哪个重载operator==
,因为将e
隐式转换为KlingonType::type
(通过operator type() const
(和将Klingon::Qapla
转换为隐式Klingon
(通过Klingon(type)
(都需要一次转换。
要求explicit
operator type() const
将修复此错误。当然,explicit
在C++03中不存在。这意味着你必须按照@Yakk评论中的建议去做,并使用类似于inner
类型的安全布尔习语。完全删除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); }
修复上述问题后,语义上几乎没有明显的差异(可以忽略隐式转换(。我发现的唯一区别是 C++11 中<type_traits>
的std::is_pod<Klingon>::value
值。使用 C++03 版本,这将是false
,而使用 enum class
es,这将是true
。在实践中,这意味着(无需优化(使用enum class
的Klingon
可以在寄存器中携带,而like_enum
版本则需要在堆栈上。
因为您没有指定enum class
的基础表示形式,所以两者sizeof(Klingon)
可能相同,但我不会依赖它。毕竟,不同实现选择的基础表示的不可靠性是强类型enum
背后的动机的一部分。
以下是上述两段关于 clang++ 3.0+、g++ 4.5+ 和 msvc 11+ 的证明。
现在就编译输出而言,两者显然都会有不兼容的 ABI。这意味着您的整个代码库需要使用其中一个。他们不会混合。对于我的系统(OSX 上的 clang++-3.5(,上述函数的符号对于 C++03 版本__Z1f9like_enumI11KlingonTypeNS0_4typeEE
,对于 C++11 版本__Z1f7Klingon
。仅当这些是导出的库函数时,这才应该是一个问题。
将优化转换为 -O2
后,输出的程序集在我对 clang++ 和 g++ 的测试中是相同的。想必其他优化编译器也将能够解包Klingon
KlingonType::type
。如果没有优化,enum class
版本当然仍然会避免所有的构造函数和比较运算符函数调用。
- 不带大括号的枚举形式
- 枚举环境变量的惯用C++14/C++17方法
- 类似枚举的计算常量
- 如何正确实现和访问运算符的各种自定义枚举器
- 错误:从"int"到枚举c++的转换无效
- C++中构造函数中的枚举
- 访问在 C++ 结构中声明的枚举变量
- 枚举类'classname'的多重定义
- 强枚举类型定义:Clang Bug 还是 C++11 标准不确定性?
- typedef 枚举和枚举类有什么区别?
- 为什么我的开关/机箱在使用枚举时默认?
- 标准::可选枚举的比较运算符
- C++两个源文件之间共享的枚举的静态实例
- 打印没有铸件的枚举可以在C++中吗?
- 枚举成员与静态 int 成员?
- C++:枚举:错误:应使用标识符而不是"}"
- 带有 c++ 的枚举(输入检查)
- 在 qml 中使用 Q_ENUM 和 Q_PROPERTY 作为枚举类
- 为什么 int 类型的枚举类值不能用作 int
- 对于从 C++03 枚举到 C++11 枚举类的源向后兼容迁移,此模式是否可以