这种模式是否适用于从C++ 03枚举到C++ 11枚举类的源向后兼容迁移?

pae*_*bal 11 c++ migration enums c++11

我们即将(在接下来的两年内)将所有编译器迁移到C++ 11-ready编译器.

我们的客户将使用我们的标题,现在我们可以为新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; }
};
Run Code Online (Sandbox Code Playgroud)

这将使我们能够升级我们的枚举,而无需在用户代码中进行不必要的更改:

//    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 ;
   }
}
Run Code Online (Sandbox Code Playgroud)

注意:LikeEnum的灵感来自Type Safe Enum成语

注2:源兼容性不包括编译错误,因为隐式转换为int:这些被认为是不合需要的,并且将提前通知客户端进行显式的整数转换.

tcl*_*amb 5

简短的回答是肯定的,这是一个可行的解决方案(只需一次修复).

这是一个很长的答案.:)


严格来说,您的比较函数存在编译时错误.这将导致符合标准的编译器出现可移植性问题.特别要考虑以下因素:

bool foo(Klingon e) { return e == Klingon::Qapla }
Run Code Online (Sandbox Code Playgroud)

编译器不应该知道operator==要使用哪个重载,因为转换eKlingonType::type隐式(via operator type() const)和转换Klingon::QaplaKlingon隐式(via Klingon(type))都需要一次转换.

要求operator type() constexplicit将修复这个错误.当然,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; }
Run Code Online (Sandbox Code Playgroud)

你还需要:

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); }
Run Code Online (Sandbox Code Playgroud)

在修复上述内容之后,语义上几乎没有明显的差异(忽略隐式转换是可能的).我发现的唯一区别是C++ 11 中std::is_pod<Klingon>::valuefrom 的值<type_traits>.使用C++ 03版本,这将是false,而使用enum classes,这将是true.在实践中,这意味着(没有优化)可以在寄存器中携带Klingon使用enum class,而like_enum版本将需要在堆栈上.

因为你没有指定的底层表示enum class,sizeof(Klingon)将可能是两个相同的,但我不会依赖于它.不同实现所选择的底层表示的不可靠性enum毕竟是强类型s 背后的动机的一部分.

以下是 clang ++ 3.0 +,g ++ 4.5+和msvc 11+的上述两段证明.


现在就编译输出而言,两者显然都会有不兼容的ABI.这意味着您的整个代码库需要使用其中一个或另一个.他们不会混在一起.对于我的系统(OSX上的clang ++ - 3.5),上面的函数符号__Z1f9like_enumI11KlingonTypeNS0_4typeEE用于C++ 03版本和__Z1f7KlingonC++ 11版本.如果这些是导出的库函数,这应该只是一个问题.

在将优化转换-O2为我的clang ++和g ++测试后,输出的程序集是相同的.想必其他优化编译器也可以解开KlingonKlingonType::type.如果没有优化,enum class版本仍然会避免所有构造函数和比较运算符函数调用.