Reputation: 27455
I'm new to C programming and trying to write a simple example. Percisely I tried to abstract over a type implementation and simply use typedef
and specify operations I can do with this type. I understand that at that point the type is incomplete, but I was intended to complete it into c
-file, not header. Here is it:
test.h
#ifndef _TEST_H
#define _TEST_H
typedef my_type_t;
void init(my_type_t **t);
#endif //_TEST_H
test.c
#include <stdlib.h>
#include "test.h"
// implementation details
struct my_type_t{ //<---- completening my_type_t to be a struct with 1 field
int field;
};
void init(struct my_type_t **t){ //<--- error: conflicting type for init
*t = malloc(sizeof(struct my_type_t));
(*t) -> field = 42;
}
Is something like this possible? I wanted the implementation completely hide all the details about the actual type definition exposing only operations that can be done with it.
UPD: If we rewrite the c
-file as follows:
#include <stdlib.h>
#include "test.h"
struct internal_my_type_definition_t{
int field;
};
void init(my_type_t **t){
struct internal_my_type_definition_t *st = malloc(sizeof(struct internal_my_type_definition_t));
st -> field = 42;
*t = st;
}
Is there any problem with such an implementation?
Upvotes: 1
Views: 79
Reputation: 215115
The design pattern you are looking for is called "opaque type"/"opaque pointers".
You almost have it correctly, you just need to specify the type explicitly in the header:
typedef struct my_type_t my_type_t;
This is both a typedef and a forward declaration of an incomplete type, which is completed in your .c file and not visible to the caller.
Now the caller can declare pointers to this type, but not objects. They can't access struct members - we've achieved private encapsulation. You have to design your functions to always take a pointer type.
Upvotes: 1
Reputation: 17532
In your header, change
typedef my_type_t;
to
struct my_type_t;
It's a pretty common pattern. Just keep in mind that you'll need a function to allocate the struct on the heap and free it; one of the pieces of information you're hiding is the size of the struct, so the API consumer can really only deal with pointers to the struct not the struct itself.
The idiomatic API would be something like
struct my_type_t* my_type_new(void);
void my_type_free(struct my_type_t* self);
my_type_init
would typically be used to initialize an already allocated instance, which is really only useful if you want to chain up to it in the *_new
function of a subtype.
Edit: in response to your follow-up question, you could conceivably do something like this in your header:
#if !defined(MY_TYPE_NS)
# define MY_TYPE_NS struct
#endif
typedef MY_TYPE_NS my_type_t my_type;
my_type* my_type_new(void);
/* ... */
Then, in your *.c file:
#define MY_TYPE_NS union
#include "test.h"
union my_type_t {
/* ... */
};
my_type* my_type_new(void*) {
my_type* res = malloc(sizeof(my_type));
res->field = 42;
return res;
}
Which I find to be only slightly evil. I'd probably just use a union nested inside of the struct to avoid any surprises in the code.
Upvotes: 4