如何在不同平台上识别非标准的C++代码?

3

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中他们不知道),自己的互斥保护等等。当实现一个干净的C++11变体时,将旧(但值得信赖)实现包装在一些#if __is_extended (最好:__is_idosyncratic!__is_ANSI_C11)。新的C++11在#else后面。当仍然编译为C++0x或C++98的翻译单元包括stuff.h时,什么都没有改变。没有编译错误,运行时没有不同的行为。 C++11仍然是实验性的。该代码可以安全地提交到主分支,同事可以研究它,从中学习并应用其组件技术。


5
你从这样的宏中获得了什么?非标准扩展因编译器而异,所以你只知道某些扩展被激活。 - m.s.
1
我不理解这个问题。你能给一个具体的例子说明你会如何使用 __is_extendedresult 吗? - Christian Hackl
@AndreasSpindler:但是这种方法比使用带有严格一致性标志的编译器更好在哪里呢?例如,如果您使用/Ze标志,则会定义_MSC_EXTENSIONS。解决此问题的方法不是使用/Ze而是使用/Za。为什么您要先启用编译器中的扩展,然后将自己的编译器配置视为错误呢? - Christian Hackl
@AndreasSpindler:我理解这个问题,但是我并不明白编译历史代码时使用/Za参数,并通过生成的错误消息精确指向代码中的非标准部分,为何不能更优雅地解决它。 - Christian Hackl
2
定义上述标记会使您的程序在标准下无效。我也看不出您如何有用地使用它。 - Yakk - Adam Nevraumont
显示剩余6条评论
3个回答

3

实际上,你的问题是反向的,因为编译器支持的非标准扩展是特定于该编译器的 - 通常甚至特定于特定的编译器版本 - 就像每个编译器定义的非标准宏一样,以便可以检测到它们。

通常的技术是相反的:指定您想要的某些功能,将其与某些宏相关联,并且只有在定义了相关联的宏时才编写使用该功能的代码。

假设存在一些古怪的功能,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) 框架,它有一组可移植性宏,在这里有文档说明。
如果您关心大量非标准特性,则使用这些宏并不是一项易如反掌的工作,因为需要了解哪些编译器(或库)的哪个版本支持每个特性,并在发布新的编译器、新的库甚至补丁时更新宏。
还需要避免在宏命名中使用保留标识符,并确保宏名称唯一。以双下划线开头的标识符是保留的。

1
一般来说,这很难做到,因为如果您依赖于不符合标准的编译器,那么就没有标准化的方法来要求仅使用标准规则(非标准编译器的行为未在标准中指定)。
您可以添加额外的构建步骤或提交挂钩,并通过特定的可移植编译器(如g ++)使用特定的严格符合性选项来传递代码。

是的,这适用于整个翻译单元。但你必须改变构建系统。如果__is_extended检查变成永久性的,我也更喜欢那样。但有时使用预处理器会更快。 - Andreas Spindler

0

首先,你不能像这样命名变量(#define __is_extended),因为以两个下划线开头的名称是为实现保留的。

你现在使用的方法仍然依赖于编译器,可能会失败:除了 __cplusplus 之外,那些宏都不是标准的,因此实现没有必要定义它们。此外,该测试基本上检查的是使用的编译器,而不是是否正在使用某些扩展。

我的建议是简单地不使用扩展。实际上很少需要它们。如果你仍然想确保它们不会被使用,可以将编译器标志设置为限制扩展的使用;对于 GCC,在 "选项控制 C 语言方言" 部分有一整章关于此问题的说明。


(1) 只有在使用某些扩展时,才会真正定义_MSC_EXTENSIONS和__STRICT_ANSI__和__EXTENDED__。 (2) 是的-自C ++ 11以来,扩展的用途变少了。但是,还有许多C ++ 98代码需要缓慢迁移。 (3) 即使gcc-Wall-Wextra-pedantic也不会抱怨带有下划线的名称,因此我想我们是站在安全的地面上 :) - Andreas Spindler
@AndreasSpindler 1) 它们不需要被定义。对于gcc/clang,带有扩展名的示例代码不会定义它们。只有在指定了-ansi-std时,__STRICT_ANSI__才存在。3) 这是未定义行为。不需要进行诊断,编译器可以随意处理它。请参见此处 - edmz
我不明白你所说的“它们不需要被定义”的意思。只要手册说明特定的宏在某些编译器选项下被定义,我们就可以使用它,对吧?例如,当使用-ansi-std=c++98-std=c++11时,__STRICT_ANSI__被定义,但是在-std=gnu++11中不被定义。这很有效。可以通过类似于touch empty.cpp; gcc -std=c++11 -dM -E empty.cpp | grep __STRICT的方式快速测试。 - Andreas Spindler
@AndreasSpindler 是的,你可以这样做。但是,它能保证明天还存在吗?不能。在另一个实现中是否保证被定义?不是。需要有意义吗?不是。等等。你明白了。 - edmz
抱歉,我的英语有歧义。 “used”实际上是指使用编译器开关。 - Andreas Spindler
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接