Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C programming decoupling interface from implementation with struct forward declaration

Tags:

c

I am writing a C program and using gcc 4.4.6 to compile. I do not want to use a c++ compiler.

I am implementing a component and I intend to have several instances of this component live and owned by other components at runtime.

As a means of decoupling the definition of an interface from its implementation and hide the internal structures and datatypes it uses in that implementation I looked to use a forward struct declaration.

Interface file: component.h

struct _hidden_implementation_type;
typedef struct _hidden_implementation_type visible_type_to_clients;

int component_function1(visible_type_to_clients instance);

Implementation file: component.c

struct _hidden_implementation_type
{ 
    int foo;
};

Client file: main.c

int main(int argc, char** argv)
{
    visible_type_to_clients a;
    return component_function1(a);
}  

How do I make this work? What other approach is there to allow multiple component instantiation and provide a decoupling between the public interface and implementation otherwise?

like image 386
Rire1979 Avatar asked Feb 09 '16 15:02

Rire1979


2 Answers

You're almost there. Your interface has to be in terms of pointers to the opaque type:

struct hidden_implementation_type;
typedef struct hidden_implementation_type visible_type_to_clients;

int component_function1(visible_type_to_clients *instance_type);

and:

int main(void)
{
    visible_type_to_clients *a = 0;
    return component_function1(a);
}

This will at least compile — it won't do anything useful, though. You'd probably need a function such as:

visible_type_to_clients *new_visible(void);

to create a value of the type and return a pointer it, and then you can use:

int main(void)
{
    visible_type_to_clients *a = new_visible();
    return component_function1(a);
}

Basically, the clients won't be able to create structures on the stack (or global structures) of your type because you've not told the compiler how big the type is. But you can deal in pointers — and typed pointers are vastly safer than 'untyped' void * pointers.

I left out error checking for simplicity. I redid the structure tag name without the leading underscore because for all practical purposes, names starting with an underscore are reserved for the implementation. I'd go with:

typedef struct VisibleType VisibleType;

where the tag and the type name are the same.

like image 70
Jonathan Leffler Avatar answered Sep 21 '22 14:09

Jonathan Leffler


Having structs hidden has advantages and disadvantages. A hidden struct can never be allocated by the client without constructor. A hidden struct requires a destructor and the client is required to remember calling it. This is an advantage or a disadvantage depending on your requirement.

Here are two implementations for comparison:

#include <stdio.h>
#include <stdlib.h>

/*VIVIBLE.h*/
typedef struct
{
    int number;
}VISIBLE;
void VISIBLE_INIT(VISIBLE * me, int num);
void VISIBLE_PRINT(const VISIBLE * me);

/*VIVIBLE.c*/
void VISIBLE_INIT(VISIBLE * me, int num) { if(me) me->number = num; }
void VISIBLE_PRINT(const VISIBLE * me) { if(me) printf("%i\n", me->number); }

/*SECRET.h*/
struct CLIENT;
void CLIENT_CTOR(struct CLIENT ** me, int num);
void CLIENT_DTOR(struct CLIENT ** me);
void CLIENT_PRINT(const struct CLIENT * me);

/*SECRET.c*/
typedef struct CLIENT
{
    int number;
}CLIENT;
void CLIENT_CTOR(CLIENT ** me, int num)
{
    if (me)
    {
        *me = (CLIENT*)malloc(sizeof(CLIENT));
        (*me)->number = num;
    }
}
void CLIENT_DTOR(CLIENT ** me)
{
    if (me && *me) free(*me);
    *me = 0;
}
void CLIENT_PRINT(const CLIENT * me) { if(me) printf("%i\n", me->number); }

/*main.c*/
void visible()
{
    VISIBLE vis; // client can allocate memory
    VISIBLE_INIT(&vis, 4);
    VISIBLE_PRINT(&vis);
    //if there is no need for a destructor the client does not need to call one
}

void hidden()
{
    CLIENT * hidden; 
    CLIENT_CTOR(&hidden, 3);
    CLIENT_PRINT(hidden);
    CLIENT_DTOR(&hidden); //Client is never allowed to forget the destructor
}

int main()
{
    visible();
    hidden();
}
like image 43
Johannes Avatar answered Sep 22 '22 14:09

Johannes