C结构体typedef与前向声明

4

在我的应用程序中,我到处都在使用结构的typedef。当我开始将代码重构为多个头文件时,代码变得笨拙。我注意到我需要预先声明Object和Klass。令人惊讶的是,我无法预先声明Object或Klass。这是因为,如您可以在Object和Klass结构中看到的那样,我正在使用Object和Klass的typedef。

//Klass.h

typedef struct Klass Klass_t;

struct Klass
{
    void (*_initialize)(Object_t* object, Klass_t* klass);
};

//Object.h

typedef struct Object Object_t;

struct Object
{
    Klass_t* _klass;
};

起初,使用typedef非常好。但是尝试前向声明Object时:

struct Object_t;

无法按照我需要的方式工作,因为需要重写函数声明:

void (*_initialize)(struct Object_t* object, Klass_t* klass);

因此,我决定在Klass.h头文件中使用typedef Object:
typedef struct Object Object_t;

当所有头文件都被引用到我的Main.c文件中时,它会抱怨:

Object.h:5: error: redefinition of typedef 'Object_t'

因此,我决定放弃所有结构体typedef,并显式声明我的结构体。

是否有一种方法可以在另一个文件中typedef结构体并进行前向声明,而不必显式使用struct Object?

我希望将结构体typedef保留在声明结构体的头文件中。如果我必须将所有typedef分组到一个头文件中,那么我宁愿根本不使用typedef。无论如何,感谢您的时间。

2个回答

5
您忘了使用struct关键字。应该是:
typedef struct Object Object_t;

以这种方式进行前向声明(但请参见下面的内容),应该始终是可能的。这样可以同时前向声明typedef标识符和struct标签。
只需在实际声明之前放置所有这些结构的前向声明即可。只要在声明中仅使用这些结构的指针,一切都应该没问题。
吹毛求疵:以“_t”结尾的名称由POSIX保留。这意味着您应该避免使用它,因为某天在某个平台上可能会预定义一个与您的类型冲突的Object_t。
我个人更喜欢以下约定。
typedef struct Object Object;

因此,无论是否带有struct,单词“Object”始终指同一对象。


谢谢!我之前不知道POSIX。由于可移植性对我很重要,我会考虑另一种命名约定。 - joe.ds

4

请记住,typedef只是类型的替代名称。Linux内核不使用typedef作为结构类型的原因是有道理的,而您正在证明这一点。

有几种方法可以解决您的问题。我注意到C11允许同一typedef的多个出现,但我假设您被困在不支持该功能的旧编译器中。

TL;DR

尽管Linux内核不使用typedef,但我通常使用typedef,但我大多避免相互引用的结构类型(我无法想到任何使用此类类型的代码)。正如Jens Gustedt在他的答案中所指出的那样,我几乎总是使用以下符号:

typedef struct SomeTag SomeTag;

为了使类型名称和结构标记相同(它们位于不同的命名空间中)。在C++中,这个操作是不必要的;当您定义struct SomeTagclass SomeTag时,名称SomeTag成为一种类型名称,无需显式的typedef(虽然typedef没有伤害,除了揭示作者在C中比C ++更有经验,或者代码起源为C代码)。

我还观察到,以下划线开头的名称最好被视为“保留给实现”。规则比这复杂一些,但当您使用以下划线开头的名称时,实现有权夺取您的名称,并且在其权利范围内夺取您的名称,因此请勿使用。同样,如果包括任何POSIX标头(例如<stdio.h>),则POSIX将类型名称结尾_t保留给实现,而您不是仅在严格的标准C模式下编译。避免创建这样的名称;它们迟早会伤害你。(我还没有修复下面的代码来处理这两个问题:caveat emptor!)。

在下面的代码片段中,我大多忽略了防止多次包含的代码(但您应该在自己的代码中使用它)。

额外标题

typedefs.h:

typedef struct Object Object_t;
typedef struct Klass  Klass_t;

klass.h

#include "typedefs.h"

struct Klass
{
    void (*_initialize)(Object_t *object, Klass_t *klass);
};

object.h

#include "typedefs.h"

struct Object
{
    Klass_t *_klass;
};

这个方法可行是因为在使用前,两个类型名称Klass_tObject_t被声明了。

在原型中使用struct Object

klass.h

typedef struct Klass Klass_t;
struct Object;

struct Klass
{
    void (*_initialize)(struct Object *object, Klass_t *klass);
};

或者,为了保持一致性,甚至可以使用:

    void (*_initialize)(struct Object *object, struct Klass *klass);

object.h

#include "klass.h"

struct Object
{
    Klass_t *_klass;
};

这是因为(在广泛的限制内,基本上,如果类型在文件作用域中定义,而不是在函数内部定义),无论所有细节是否完全定义,struct Object始终引用相同的类型。
GCC 4.8.2在所有-std=c89-std=c99-std=c11下都接受重复的typedef,如下所示的代码。它需要-std=c89 -pedantic-std=c99 -pedantic才能得到关于重复的typedef的错误。
即使没有-pedantic选项,GCC 4.5.2也会拒绝此代码;然而,GCC 4.6.0及更高版本在没有-pedantic选项的情况下也会接受它。

klass.h

#ifndef KLASS_H_INCLUDED
#define KLASS_H_INCLUDED

typedef struct Klass Klass_t;
typedef struct Object Object_t;

struct Klass
{
    void (*_initialize)(Object_t *object, Klass_t *klass);
};

#endif /* KLASS_H_INCLUDED */

object.h

#ifndef OBJECT_H_INCLUDED
#define OBJECT_H_INCLUDED

typedef struct Klass Klass_t;
typedef struct Object Object_t;

struct Object
{
    Klass_t *klass;
};

#endif /* OBJECT_H_INCLUDED */

consumer.c

#include "klass.h"
#include "object.h"

Klass_t k;
Object_t o;

您需要决定是否愿意为您的代码承担这种风险 - 可移植性有多重要,必须使其可移植到哪些版本的C(以及哪些C编译器)。


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