I was using typedef for structures everywhere in my application. I then started to refactor into several
header files when it started to get clunky. I noticed I needed to forward declare Object, and Klass.
Well, to my surprise, I couldn't forward declare Object, or Klass. This is because, as you can see in the
Object and Klass structure, I'm using a typedef of Object and Klass.
//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;
};
At first, using typedef was great. But trying to forward declare Object:
struct Object_t;
Doesn't work as I would need to rewrite by function declarations as:
void (*_initialize)(struct Object_t* object, Klass_t* klass);
So I decided to just typedef Object inside the Klass.h header file:
typedef struct Object Object_t;
Well when all header files are included into my Main.c file, it complians:
Object.h:5: error: redefinition of typedef 'Object_t'
So, I then decided to just drop all struct typedefs and explicity declare my structures.
Is there a way to typedef a structure and forward declare in another file without explicitly using struct Object?
I want to keep structure typedefs inside the header file where
the structure is declared. If I have to group all typedefs inside one header file then I would rather not use typedef at all. Anyways, thanks for your time.
解决方案
Remember that a typedef is just an alternative name for a type. There's a reason the Linux kernel doesn't use typedefs for structure types, and you're demonstrating it.
There are a couple of ways around your problem. I note that C11 does allow multiple occurrences of the same typedef, but I'm assuming you're stuck with an older compiler that does not support that.
TL;DR
Even though the Linux kernel doesn't use typedefs, I usually do use typedefs, but I mostly avoid mutually referential structure types (I can't think of any code where I used such a type). And, like Jens Gustedt notes in his answer, I almost invariably use the notations:
typedef struct SomeTag SomeTag;
so that the type name and the structure tag are the same (they're in different namespaces). This operation is not necessary in C++; when you define struct SomeTag or class SomeTag, the name SomeTag becomes a type name without the need for an explicit typedef (though a typedef does no harm other than revealing that the author is more experienced in C than C++, or the code originated as C code).
I also observe that names starting with an underscore are best treated as 'reserved for the implementation'. The rules are a little more complex than that, but you run a risk of the implementation usurping your names — and being within its rights to usurp your names — when you use names starting with an underscore, so don't. Likewise, POSIX reserves type names ending _t for the implementation if you include any POSIX headers (such as when you aren't compiling in strict Standard C only mode). Avoid creating such names; they'll hurt you sooner or later. (I've not fixed the code below to deal with either of these issues: caveat emptor!).
In the code fragments below, I'm mostly ignoring the code that prevents multiple inclusions (but you should have it in your code).
Extra header
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;
};
This works because the two type names Klass_t and Object_t are declared before they're used.
Use struct Object in prototype
klass.h
typedef struct Klass Klass_t;
struct Object;
struct Klass
{
void (*_initialize)(struct Object *object, Klass_t *klass);
};
Or, for consistency, it might even use:
void (*_initialize)(struct Object *object, struct Klass *klass);
object.h
#include "klass.h"
struct Object
{
Klass_t *_klass;
};
This works because (within broad limits — basically, if the types are defined at file scope, not inside a function) struct Object always refers to the same type, regardless of whether all the details are fully defined yet.
GCC 4.8.2
Under all of -std=c89, -std=c99 and -std=c11, GCC 4.8.2 accepts replicated typedefs, as in the code below. It requires -std=c89 -pedantic or -std=c99 -pedantic to get errors about the repeated typedefs.
Even without the -pedantic option, GCC 4.5.2 rejects this code; however, GCC 4.6.0 and later versions accept it without the -pedantic option.
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;
You'll have to decide whether that's a risk you're willing to take for your code — how important is portability, and to which versions of C (and which C compilers) must it be portable.