在C/C++中,是否有类似于#ifndef的指令用于typedef?

54

如果我想定义一个值,但仅在该值未定义时才这样做,我会像这样处理:

#ifndef THING
#define THING OTHER_THING
#endif

如果THING是一个typedef标识符而不是已定义的变量,我该怎么办?我想像这样做:

#ifntypedef thing_type
typedef uint32_t thing_type
#endif

问题出现的原因是我想检查一个外部库是否已经定义了boolean类型,但我愿意听取更一般的解决方案。


7
只是提供信息,如果你的编译器是MSVC,C++中有__if_not_exists这个功能。例如,可以使用以下代码: __if_not_exists( thing_type ) { typedef uint32_t thing_type; } 来定义一个新类型。 - Ise Wisteria
2
在C11和C++中,您可以将typedef重新定义为相同的内容。 - M.M
13个回答

26

语言本身没有这种东西,也不需要。在一个项目中,你永远不应该有相同的typedef别名引用不同的类型,因为这是ODR的违规行为,如果你要为同一类型创建相同的别名,那就直接这样做就可以了。语言允许你执行任意次数的相同typedef,并且通常会捕获到特定的ODR问题(在同一个翻译单元中):

typedef int myint;
typedef int myint;       // OK: myint is still an alias to int
//typedef double myint;  // Error: myint already defined as alias to int
如果你想通过使用typedef来确定使用哪个类型来实现不同类型的功能,那么你应该考虑使用模板而不是typedef。

2
注意:对于C语言而言,即使两个typedef别名相同的类型,声明typedef超过一次也是无效的。请参见https://dev59.com/xmsy5IYBdhLWcg3wtwQd#8367810。 - Greg A. Woods
10
不是真的。在编写库时,这很容易发生。当一个函数应该返回布尔值时,在头文件中应该使用什么?如果你的库被用于一个 X 项目中,那么 X 已经定义了 BOOL。如果你在另一个项目中使用你的库,那么可能会有一种不同的、完全不兼容的 Bool 定义。 - swestrup
@GregA.Woods C11 将此功能添加到了 C 语言中;您链接的答案仅适用于 C99。 - M.M
这个功能对于一个需要在不同平台上使用C89和C99编译器而无需更改代码的项目非常有用,同时希望添加一些C99中的typedefs,例如uintmax_t。如果typedef尚未可用,则头文件可以模拟它。 - khw

22

C ++没有提供任何机制让代码测试typedef的存在,您能做到的最好的方式是像这样:

#ifndef THING_TYPE_DEFINED
#define THING_TYPE_DEFINED
typedef uint32_t thing_type 
#endif

编辑:
正如@David在评论中所正确指出的那样,这回答了“如何?”的问题,但重要的是错过了“为什么?”的部分。如果你想要做到这一点,可以按照上面的方式进行,但重要的是你可能根本不需要这样做。@David的回答和评论解释了细节,我认为这正确回答了问题。


9
那太丑陋无用了。重要的问题不是如何做到这一点,而是为什么要这样做?答案是你不应该这样做。在同一翻译单元中,typedef可以被重新定义多次(只要它始终别名于相同的类型),因此这不是一个问题。在同一程序的不同翻译单元中定义它以别名于不同的类型是违反ODR的,因此也不是你想要的。如果别名始终相同,你最好什么也不做,或者在编译时根据之前包含了什么来隐藏错误。 - David Rodríguez - dribeas
1
@dribeas - David Rodríguez:我同意,我认为我错过了一个“为什么?”的问题,直接进入了“如何?”的部分。我将在同一处添加注释。 - Alok Save

11

在C++的预处理阶段中没有这样的功能。最多只能做到:

#ifndef thing_type
#define thing_type uint32_t 
#endif

虽然这不是好的编程实践,我不建议这样做。


14
我没有点踩,但我更喜欢别人告诉我答案哪里错了,而不仅仅是点个踩。这种方法的问题在于typedef创建了一个真正的类型别名,而宏只是纯文本替换。在这个例子中并不重要,但是 void foo(const X x) 的语义取决于 Xtypedef还是上面的宏定义:typedef int* X 将使函数变为 void foo( int * const ),而 #define X int* 则会使其变成 void foo( int const * )(可恶的左边的const)。 - David Rodríguez - dribeas
宏定义(defines)不等同于类型定义(typedefs)。事实上,宏定义应该少用于类型定义。这是一种不好的编码习惯。 - DevSolar
@DevSolar,我不支持这种技术;这只是其中一种方法。 - iammilind

6
预处理指令(例如#define)是粗糙的文本替换工具,它们对编程语言一无所知,因此不能作用于任何语言级别的定义。
确保类型仅被定义一次有两种方法:
  • 组织代码以使每个定义都有其位置,不需要多个定义。
  • #define一个预处理宏和类型一起使用,使用#ifndef在定义类型之前检查宏定义。
第一种选择通常会导致更易于维护的代码。如果您在一个程序内意外地得到不同的类型定义,则第二种可能会导致微妙的错误。

2
正如其他人已经说过的,这种东西并不存在,但是如果你尝试为不同类型创建一个别名,你会得到编译错误:
typedef int myInt;
typedef int myInt;    // ok, same alias
typedef float myInt;  // error

然而,有一种叫做ctag的东西可以用来查找typedef定义的位置。

感谢提到ctags。这对我来说是新的。 - Josh Sanford

2
这可能不是直接回答问题的方法,但可以作为您问题的可能解决方案。
为什么不尝试这样做?
#define DEFAULT_TYPE int // just for argument's sake
#ifndef MY_COOL_TYPE
     #define MY_COOL_TYPE DEFAULT_TYPE
#endif
typedef MY_COOL_TYPE My_Cool_Datatype_t;

如果您想自定义类型,则可以在此之前的某个地方(比如在包含在此头文件顶部的“configure”头文件中)定义MY_COOL_TYPE,或者在编译时将其作为命令行参数传递(据我所知,您可以使用GCC和LLVM等编译器进行此操作,也许其他编译器也可以)。


2
这个问题非常讨厌,因为一些API或SDK重新定义了常用的东西。我遇到过一个问题,地图处理软件(GIS)的头文件重新定义了TRUE和FALSE(通常由Windows SDK使用)关键字为整数文字而不是true和false关键字(显然,这可能会破坏某些东西)。是的,“#define true false”这个经典笑话可以引用。

预处理器不会感知在C / C ++代码中声明的typedef或常量,因为它不分析代码,只扫描#语句。它在将代码提供给语法分析器之前修改代码。因此,一般来说是不可能的。

https://msdn.microsoft.com/en-us/library/5xkf423c.aspx?f=255&MSPPError=-2147217396 该语句目前在不可移植,尽管已有要求在GCC中实现它。我认为,在MSVC中也算作“扩展”。这是一个编译器语句,而不是预处理器语句,因此它不会“感知”定义的宏,它只会检测函数体外部的typedef。那里的“full type”表示它将对完整的定义做出反应,忽略像“class SomeClass;”这样的声明。自行决定是否使用。

编辑:显然,它现在还在MacOS上得到支持,并且通过Intel编译器的-fms-dialect标志 (AIX\Linux?)


1
没有你想要的东西。我曾经遇到过包含自己的typedefs(如bool)的库与你同样的问题。当它们不关心你用什么来表示bool或其他库可能会做同样的事情时,这就成为了一个问题!!
所以这是我做的。我编辑这些库的头文件并找到typedef bool,然后添加以下代码:
#ifdef USE_LIBNAME_BOOL
typedef unsigned char bool; // This is the lib's bool implementation
#else
#include <stdbool.h>
#endif

请注意,如果我不想使用库的自有 bool typdef,则需要包含。这意味着您需要 C99 支持或更高版本。

0

这是一个好问题。C语言和Unix有着悠久的历史,许多Unix C typedef在非POSIX平台(例如Windows,嘘,不要告诉Cygwin的人)上不可用。每当你尝试编写可在这些系统之间移植的C语言时,你都需要决定如何回答这个问题(嘘,不要告诉Cygwin的人)。

如果您需要跨平台可移植性,请了解编译目标的特定于平台的宏定义有时会有所帮助。例如,Windows有定义为_WIN32的预处理器宏 - 它在编译目标为32位ARM、64位ARM、x86或x64时为1。但是它的存在也告诉我们,我们在Windows机器上。这意味着例如ssize_t将不可用(不是size_t)。因此,您可能需要执行类似以下操作:

#ifdef _WIN32
typedef long ssize_t;
#endif

顺便说一下,在这个线程中,人们评论了一个形式上称为Guard(卫兵)的相似模式。在头文件(即接口或“.h”文件)中经常看到它以防止多次包含。你会听到有关头文件保护的内容。

/// @file poop.h

#ifndef POOP_H
#define POOP_H

void* poop(Poop* arg);

#endif

现在我可以在实现文件poop.c和其他文件(如main.c)中包含头文件,并且我知道它们将始终成功编译并避免多次包含,无论它们是一起编译还是单独编译,这要感谢头文件保护。

有经验的程序员会使用C++11函数式宏或编程方式来编写头文件保护。如果你喜欢阅读书籍,我推荐Jens Gustedt的《现代C语言》。


0

如前所述,这不包含在C++标准中,但您可能可以使用autotools来获得相同的功能。

您可以使用{{link1:ac_cxx_bool}}宏来确保定义了bool(或不同数据类型的不同例程)。


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