Reputation: 125
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.
Upvotes: 4
Views: 11758
Reputation: 755064
Remember that a typedef
is just an alternative name for a type. There's a reason the Linux kernel doesn't use typedef
s 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.
Even though the Linux kernel doesn't use typedef
s, I usually do use typedef
s, 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 <stdio.h>
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).
typedef struct Object Object_t;
typedef struct Klass Klass_t;
#include "typedefs.h"
struct Klass
{
void (*_initialize)(Object_t *object, Klass_t *klass);
};
#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.
struct Object
in prototypetypedef 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);
#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.
Under all of -std=c89
, -std=c99
and -std=c11
, GCC 4.8.2 accepts replicated typedef
s, as in the code below. It requires -std=c89 -pedantic
or -std=c99 -pedantic
to get errors about the repeated typedef
s.
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.
#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 */
#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 */
#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.
Upvotes: 4
Reputation: 78993
You are missing the struct
keyword. It should be
typedef struct Object Object_t;
Forward declaration in that way (but see below) should always be possible. This forward declares the typedef
identifier and the struct
tag at the same time.
Just put such forward declarations of all your struct
before the real declarations. As long as you only use pointers to these struct
inside the declarations everything should be fine, then.
nitpick: names with a _t
are reserved by POSIX. This means that you should avoid it, because some day on some platform there could be a Object_t
that is predefined and will conflict with your type.
I personally prefer the following convention
typedef struct Object Object;
so the word Object, with or without struct
, always refers to the same.
Upvotes: 5