对于从 C++03 枚举到 C++11 枚举类的源向后兼容迁移,此模式是否可以

Is this pattern ok for a source backward-compatible migration from C++03 enum into C++11 enum class?

本文关键字:枚举 迁移 模式 是否 C++03 C++11      更新时间:2023-10-16

我们即将(大约在未来两年内(将所有编译器迁移到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 classKlingon可以在寄存器中携带,而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版本当然仍然会避免所有的构造函数和比较运算符函数调用。