可移植地识别非标准 C++?
Posted
技术标签:
【中文标题】可移植地识别非标准 C++?【英文标题】:Recognize non-standard C++ portably? 【发布时间】:2015-10-08 14:18:23 【问题描述】:C 有__STDC__
,但似乎没有识别某些扩展 C++ 方言的标准方法。因此,对于我使用的可移植代码
#define __is_extended \
((__GNUG__ &&!__STRICT_ANSI__) || \
(_MSC_VER && _MSC_EXTENSIONS && __cplusplus) || \
(__IBMCPP__ && __EXTENDED__))
到目前为止,这适用于 gcc、XLC 和 Visual C++。
我们必须针对每个编译器进行 ISO/ANSI 一致性测试,对吗?如果是这样,您能否为其他已被证明有效的编译器提出建议?
编辑:由于对此类测试的赞成和反对进行了很多讨论,所以这里有一个真实的例子。假设有一些头文件 stuff.h 在多个项目中与多个编译器一起广泛使用。 stuff.h 使用一些特定于编译器的 vsnprintf
(在 C++11 之前未标准化)、一些 copy_if<>
(在 C++98 中使用 they somehow missed)、自己的互斥锁以及其他别的。在实现一个干净的 C++11 变体时,您将旧的(但受信任的)实现包装在一些 #if __is_extended
中(更好的是:__is_idosyncratic
或 !__is_ANSI_C11
)。新的 C++11 落后于 #else
。当仍编译为 C++0x 或 C++98 的翻译单元包含 stuff.h 时,没有任何变化。没有编译错误,运行时没有不同的行为。 C++11 仍然是实验性的。代码可以安全地提交到主分支,同事可以研究它,从中学习并在其组件中应用技术。
【问题讨论】:
你从这样的宏中得到了什么?非标准扩展确实因编译器而异,因此您只知道 一些 扩展被激活。 我不明白这个问题。你能举一个具体的例子来说明你会用__is_extended
的结果做什么吗?
确保某些翻译单元不能使用一些语言扩展。启用像 #if __is_extended #error this is portable code #endif
这样的编译时断言,或者像 #define __is_ANSI_CPP11 (__cplusplus == 201103L && !__is_extended)
这样的宏。
@AndreasSpindler:但这比简单地使用严格一致性标志调用编译器更好吗?例如,如果您使用/Ze
标志,将定义_MSC_EXTENSIONS
。这个问题的解决方案不是使用/Ze
,而是使用/Za
。为什么要先在编译器中启用扩展,然后将自己的编译器配置视为错误?
定义上述标记会使您的程序在标准下格式不正确。我也看不出你会如何有效地使用它。
【参考方案1】:
您的问题实际上是向后的,因为编译器支持的非标准扩展是特定于该编译器的 - 通常在特定于特定编译器版本的范围内 - 每个编译器定义的非标准宏也是如此可以检测到。
通常的技术是相反的:指定你想要的一些特性,将它与一些宏相关联,并且如果定义了相关的宏,则只编写使用该特性的代码。
假设支持一些时髦的功能 - Visual C++ 11 和 g++ 版本 3.2.1 以完全相同的方式支持,但不支持任何其他编译器(甚至不支持其他版本的 Visual C++ 或 g++)。
// in some header that detects if the compiler supports all sorts of features
#if ((defined(__GNUG__) && __GNUC__ == 3 && __GNUC_MINOR__ == 2 && __GNUC_PATCHLEVEL__ == 1) || (defined(_MSC_VER) && _MSC_VER == 1700))
#define FUNKY_FEATURE
#endif
// and, in subsequent user code ....
#ifdef FUNKY_FEATURE
// code which uses that funky feature
#endif
有很多免费的通用库使用这种技术(显然有更好的宏命名)。想到的一个例子是ACE (Adaptive Communication Environment) framework,它有一组可移植宏,记录在here。
如果您担心大量非标准功能,那么使用此类宏不适合胆小的人,因为有必要了解哪些编译器(或库)版本支持每个功能,并且每次发布新编译器、新库甚至补丁时更新宏。
还必须避免在命名这些宏时使用保留标识符,并确保宏名称是唯一的。以双下划线开头的标识符被保留。
【讨论】:
【参考方案2】:一般来说,这很难做到,因为如果您依赖不符合标准的编译器,那么就没有标准化的方法来只需要标准规则(标准没有指定非标准编译器的行为)。
您可以做的是添加一个额外的构建步骤或提交挂钩,并通过具有特定严格一致性选项的特定可移植编译器(如 g++)传递代码。
【讨论】:
是的,这适用于整个翻译单元。但是你必须改变构建系统。如果__is_extended
支票成为永久性支票,我也会更喜欢。但有时使用预处理器会快得多。【参考方案3】:
首先,您不能以这种方式命名变量 (#define __is_extended
),因为以下划线开头的名称是为实现保留的。
您拥有的方法仍然依赖于编译器并且可能会失败:除了__cplusplus
,这些宏都不是标准的,因此不需要实现来定义它们。此外,该测试基本上是检查正在使用的编译器,而不是检查是否使用了某些扩展。
我的建议就是不要使用扩展。对它们的需求非常少。如果您仍然想确保它们无论如何都不会被使用,您可以打开编译器的标志来限制扩展的使用;对于 GCC,"Options Controlling C Dialect" 部分有一整章介绍了这一点。
【讨论】:
(1)_MSC_EXTENSIONS
和 __STRICT_ANSI__
和 __EXTENDED__
实际上只有在使用某些扩展时才被定义。 (2) 是的 - 因为 C++11 对扩展的使用较少。但是有很多 C++98 代码会慢慢迁移。 (3) 即使gcc -Wall -Wextra -pedantic
也不会抱怨带有下划线的名字,所以我想我们是安全的:-)
@AndreasSpindler 1) 不需要定义它们。此外,对于 gcc/clang,带有扩展名的示例代码没有定义它们。 __STRICT_ANSI__
仅在指定 -ansi
或 -std
时才存在。 3)它是UB。不需要诊断,编译器可以做任何它想做的事情。见this。
我不明白你所说的“他们不需要定义”是什么意思。只要手册说在某些编译器选项下定义了特定的宏,我们就可以使用它,对吗?例如,__STRICT_ANSI__
是在使用 -ansi
、-std=c++98
、-std=c++11
时定义的,但不是在使用 -std=gnu++11
时定义的。这很好用。可以使用 touch empty.cpp; gcc -std=c++11 -dM -E empty.cpp | grep __STRICT
之类的东西快速测试。
@AndreasSpindler 是的,你可以。但是,它保证明天存在吗?不。是否保证在另一个实现上定义?不,它必须是有意义的吗?不,等等。你明白了。
...即使明天也不能保证明天存在 ;-) 任何事情迟早都会改变。我发现编译器宏不会经常更改。更有可能是您的构建系统中断或某些运行时组件(数据库、服务器)改变了行为。以上是关于可移植地识别非标准 C++?的主要内容,如果未能解决你的问题,请参考以下文章