实现与C ++ 11的前向兼容性


12

我在大型软件应用程序上工作,该应用程序必须在多个平台上运行。其中一些平台支持C ++ 11的某些功能(例如MSVS 2010),而某些则不支持任何功能(例如GCC 4.3.x)。我希望这种情况会持续数年(我的最佳猜测:3-5年)。

鉴于此,我想设置一个兼容性接口,以便(尽可能)人们可以编写C ++ 11代码,而这些代码仍可以用较旧的编译器进行编译,而只需很少的维护。总体而言,目标是尽可能合理地最小化#ifdef,同时仍在支持它们的平台上启用基本的C ++ 11语法/功能,并在不支持它们的平台上提供仿真。

让我们从std :: move()开始。实现兼容性的最明显方法是将这样的内容放入公共头文件中:

#if !defined(HAS_STD_MOVE)
namespace std { // C++11 emulation
  template <typename T> inline T& move(T& v) { return v; }
  template <typename T> inline const T& move(const T& v) { return v; }
}
#endif // !defined(HAS_STD_MOVE)

人们可以这样写

std::vector<Thing> x = std::move(y);

...不受惩罚。它可以满足他们在C ++ 11中的要求,并且可以在C ++ 03中尽其所能。当我们最后删除最后的C ++ 03编译器时,此代码可以保持不变。

但是,根据标准,将新符号注入std名称空间是非法的。那是理论。我的问题是:实际上,这样做作为实现前向兼容的方式有没有害处?


1
Boost已经提供了很多这样的功能,并且已经拥有在可用时/可用时使用新功能的代码,因此您可以仅使用Boost提供的功能并完成操作。当然,有局限性-大多数新功能是专门添加的,因为基于库的解决方案还不够。
杰里·科芬

是的,我专门考虑的是可以在库级别实现的功能,而不是语法上的更改。Boost并没有真正解决(无缝)前向兼容性的问题。除非我错过了某件事...
mcmcc'4

Gcc 4.3已经具有许多C ++ 11功能,Rvalue引用可能是最重要的。
Jan Hudec 2012年

@JanHudec:你是对的。可怜的例子。无论如何,肯定有其他编译器不支持该语法(例如,我们拥有的任何版本的IBM C ++编译器)。
mcmcc 2012年

Answers:


9

我一直在努力保持C ++程序中的向前和向后兼容性,直到最终我不得不用它制作一个库工具箱,而我正准备发布工具箱。通常,只要您接受不会在功能(某些内容无法被前向仿真)或语法(就可能不得不使用宏,替代名称空间)中获得“完美”的前向兼容性。一些东西),那么你就准备好了。

有很多功能可以在C ++ 03中进行仿真,其水平足以满足实际使用,并且没有诸如:Boost之类的所有麻烦。哎呀,甚至C ++标准提案都nullptr建议C ++ 03反向移植。例如,对于所有C ++ 11,都有TR1,但是我们已经有几年的预览了。不仅如此,一些C ++ 14功能(如assert变量,透明函子)optional 可以在C ++ 03中实现!

我知道不能绝对反向移植的仅有两件事是constexpr和可变参数模板。

关于向名称空间添加内容的整个问题std,我认为这无关紧要。想想Boost(最重要,最相关的C ++库之一)及其TR1的实现:Boost.Tr1。如果要改进C ++,使其与C ++ 11向前兼容,那么根据定义,您会将其转变为非 C ++ 03的东西,因此将自己限制在打算避免或遗弃的标准之上是,简而言之,适得其反。纯粹主义者会抱怨,但从定义上讲,人们不必在乎它们。

当然,仅因为您毕竟不会遵守(03)标准并不意味着您无法尝试,或者将乐于突破该标准。那不是重点。只要您对添加到std名称空间中的内容保持非常谨慎的控制,并控制使用软件的环境(即:进行测试!),那么根本就不会有任何不可挽回的危害。如果可能的话,请在单独的命名空间中定义所有内容,并仅using向命名空间添加指令,以std使您不会在其中添加超出“绝对”需要的内容。IINM几乎与Boost.TR1的功能相同。


更新(2013):作为原始问题的请求,并由于缺少rep而看到了一些我无法添加的评论,这是C ++ 11和C ++ 14功能及其可移植程度的列表到C ++ 03:

  • nullptr:在官方委员会的支持下,完全可实施;您可能还必须提供一些type_traits专长,以便将其识别为“本机”类型。
  • forward_list完全可实现,尽管分配器支持取决于您的Tr1实现可以提供什么。
  • 新算法(partition_copy等):完全可实现。
  • 大括号序列的容器构造(例如:)vector<int> v = {1, 2, 3, 4};完全可以实现,尽管比人们想要的要复杂。
  • static_assert:当实现为宏时几乎可以实现(您只需要注意逗号)。
  • unique_ptr:几乎可以实现,但是您还需要调用代码的支持(用于将其存储在容器中,等等);见下面。
  • 右值引用:几乎完全可以实现,具体取决于您希望从中获得多少(例如:Boost Move)。
  • Foreach迭代:几乎完全可以实现,语法会有所不同。
  • 使用本地函数作为参数(例如:transform):几乎可以实现,但是语法会有所不同-例如,本地函数不是在调用站点定义的,而是在调用站点之前定义的。
  • 显式转换运算符:可实现到实际水平(使转换显式进行),请参见Imperfect C ++的“ explicit_cast”;但是与语言功能的集成(例如,static_cast<>几乎不可能)。
  • 参数转发:可以实现上述rvalue-references上的实际水平,但是您需要为使用可转发参数的函数提供N个重载。
  • 移动:可实施到实际水平(请参见以上两个)。当然,您必须使用修饰符容器和对象才能从中获利。
  • 范围分配器:除非您的Tr1实现可以提供帮助,否则它实际上无法实现。
  • 多字节字符类型:除非您的Tr1支持您,否则无法真正实现。但是出于预期目的,最好使用专门设计用于处理此问题的库,例如ICU,即使使用C ++ 11。
  • 可变参数列表:有些麻烦,请注意实参转发。
  • noexcept:取决于编译器的功能。
  • 新的auto语义和decltype:取决于编译器的功能-例如:__typeof__
  • 大小的整数类型(int16_t,等):取决于编译器的功能-或可以委托给Portable stdint.h。
  • 类型属性:取决于编译器的功能。
  • 初始化程序列表:据我所知无法实现;但是,如果要使用序列初始化容器,请参见上文“容器构造”。
  • 模板别名:据我所知,这是无法实现的,但无论如何,它是不需要的功能,并且我们::type永远在模板中使用过
  • 可变参数模板:据我所知无法实现;close是模板参数默认值,需要N个专业化信息,等等。
  • constexpr:据我所知无法实施。
  • 统一初始化:据我所知无法实现,但是可以保证默认的 -constructor初始化可以通过Boost的值初始化来实现。
  • C ++ 14 dynarray完全可实现。
  • C ++ 14 optional<>:几乎可以实现,只要您的C ++ 03编译器支持对齐设置即可。
  • C ++ 14透明函子:几乎可以实现,但是您的客户端代码可能必须显式使用eg .: std::less<void>使其起作用。
  • C ++ 14的新变种的assert(如assure):完全如果你想断言,近充分实现的,如果你想使实现的,而不是抛出。
  • C ++ 14元组扩展(按类型获取元组元素):完全可实现,甚至可以使它无法按照功能建议中描述的确切情况进行编译。

(免责声明:以上已在上面链接的C ++反向端口库中实现了其中一些功能,因此我认为当我说“完全”或“几乎完全”时,我在说什么。)


6

从根本上讲这是不可能的。考虑一下std::unique_ptr<Thing>。如果可以将右值引用模拟为一个库,那么它将不是语言功能。


1
我确实说过“尽可能”。显然,某些功能必须留在#ifdef后面或根本不使用。std :: move()恰好是您可以支持语法的(尽管不是功能)。
mcmcc 2012年

2
实际上,右值引用建议提到了基于库的解决方案!
Jan Hudec 2012年

更具体地说,可以在C ++ 03中实现特定类的移动语义,因此应该可以在其中定义std::unique_ptr,但是rvalue引用的其他一些功能无法在C ++ 03中实现,因此std::forward是不可能的。另一件事是,std::unique_ptr它将不会有用,因为除非您全部替换了集合,否则集合将不会使用move语义。
Jan Hudec

@JanHudec:无法定义unique_ptr。看看的失败auto_ptrunique_ptr实际上是一个类的教科书示例,其语义从根本上由语言功能启用。
DeadMG 2012年

@DeadMG:不,不是unique_ptr语言功能从根本上启用了它。如果没有该功能,它将不会很有用。因为如果没有完美的转发功能,在许多情况下将无法使用,完美的转发功能确实需要该功能。
Jan Hudec

2
  1. Gcc在4.3中开始引入C ++ 11(当时仍为C ++ 0x)。该表说它已经具有右值引用和一些其他较少使用的功能(您必须指定-std=c++0x选项以启用它们)。
  2. 在TR1中已经定义了C ++ 11中标准库的许多附加功能,GNU stdlibc ++在std :: tr1名称空间中提供了这些附加功能。因此,只需进行适当的条件使用即可。
  3. Boost定义了大多数TR1函数,如果没有,可以将它们注入TR1名称空间(但是,如果使用GNU stdlibc ++,则VS2010可以,而gcc 4.3也可以)。
  4. std命名空间中放置任何内容都是“未定义的行为”。这意味着规范没有说明将会发生什么。但是,如果您知道在特定平台上标准库未定义某些内容,则继续进行定义。仅期望您将必须在每个平台上检查所需的内容以及可以定义的内容。
  5. 关于右值引用建议N1690提到了如何在C ++ 03中实现移动语义。那可以用来代替unique_ptr。但是,它并不是太有用,因为它实际上依赖于使用move语义的集合,而C ++ 03显然并不。

1
您对GCC的观点是正确的,但是不幸的是,我还必须支持其他(非GCC)编译器。您的#4子弹是我要问的问题的核心。#5很有趣,但我不希望在这些旧平台上支持移动语义(复制优化),而只是将“ std :: move()”作为可编译语法。
mcmcc 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.