c++ - 对于从 C++03 枚举到 C++11 枚举类的源向后兼容迁移,这种模式是否可行?

标签 c++ c++11 enums migration

我们即将(大约在接下来的两年内)将我们所有的编译器迁移到 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== 的哪个重载使用,同时转换 eKlingonType::type隐式地(通过 operator type() const )并转换 Klingon::QaplaKlingon隐式地(通过 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++ 的测试输出的程序集是相同的 。大概其他优化编译器也将能够解包 KlingonKlingonType::type .如果不进行优化,enum class当然,版本仍将避免所有构造函数和比较运算符函数调用。

关于c++ - 对于从 C++03 枚举到 C++11 枚举类的源向后兼容迁移,这种模式是否可行?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23270788/

相关文章:

c++ - 如何在C++中读取然后编辑文件txt

c++ - 使用 clang 进行源到源转换(最先进的技术)

c++ - 错误地使用 move 的值

swift - 如何使枚举符合 Swift 中的协议(protocol)?

java - Java 中 2 位运算符的强大功能?

c++ - 当 unary_function 使用引用参数时使用 std::not1 编译时出错

c++ - 将项目添加到问题列表

java - 在Java中调用枚举变量

用于复杂类型的单一方法的 C++ 模板特化

c++ - 发布由指针管理的列表的添加和打印元素