Idiomatic module encapsulation



Hello everyone,

I'm implementing a wrapper around platform-dependent "primitives",
and I'm trying to have 0 platform-dependent code (even includes)
in the "module" interface header.

For example, consider this possible interface for a semaphore
(whatever that is):

Sem_t *Sem_create(int val);
void Sem_delete(Sem_t *sem);
int Sem_signal(Sem_t *sem);
int Sem_wait(Sem_t *sem);
int Sem_wait_timeout(Sem_t *sem, unsigned wait_ms);

I could use an opaque (void *) pointer to "hide" the details of
a (Sem_t) but I do want the compiler to throw an error when an
imprudent user calls, e.g.

char *str = "booya";
Sem_signal(str);

One method I've seen a few times, is to "lie" to the user code
(and, to some level, to the compiler) by pretending "yeah, this
Sem_t is a struct, and I'll provide the definition sometime later"
(I think it's called a tentative declaration). But I've been told
that the compiler always gets its revenge when it's lied to.

Could you please confirm whether the following approach is safe,
and does what I want?

$ cat wrap_sem.h
#ifndef TRUE_SEM_T_DEFINITION
typedef struct dummy_Sem_t Sem_t;
#endif
Sem_t *Sem_create(int val);
void Sem_delete(Sem_t *sem);
int Sem_signal(Sem_t *sem);
int Sem_wait(Sem_t *sem);
int Sem_wait_timeout(Sem_t *sem, unsigned wait_ms);

Users just include "wrap_sem.h" and they're not supposed to "peek"
inside a Sem_t (in fact, they can't since Sem_t is an incomplete type).

$ cat test.c
#include "wrap_sem.h"
int main(void)
{
Sem_t *toto = Sem_create(42);
*toto;
Sem_wait("foo");
return 0;
}

$ gcc -Wall -Wextra -std=c89 test.c
test.c: In function 'main':
test.c:5:3: error: dereferencing pointer to incomplete type
test.c:6:3: warning: passing argument 1 of 'Sem_wait' from incompatible pointer type
wrap_sem.h:7:5: note: expected 'struct Sem_t *' but argument is of type 'char *'

(Side issue: there seems to be a small QoI issue with the compiler's note,
as there is no 'struct Sem_t' AFAIU, there is a 'struct dummy_Sem_t' and
a 'Sem_t'. Do you agree? End digression)

In the actual implementation file, I define TRUE_SEM_T_DEFINITION
and then define the "real" Sem_t type.

Is this an (the?) idiomatic way to hide implementation details?
Is there a chance that this could bite me?
Could this confuse other tools, such as a debugger?
Any unforeseen consequences?

Regards.
.



Relevant Pages

  • Re: Idiomatic module encapsulation
    ... in the "module" interface header. ... (and, to some level, to the compiler) ... Sem_t is a struct, and I'll provide the definition sometime later" ... int Sem_signal ...
    (comp.lang.c)
  • Re: Idiomatic module encapsulation
    ... and I'm trying to have 0 platform-dependent code ... in the "module" interface header. ... (and, to some level, to the compiler) ... struct semaphore *sem_create; ...
    (comp.lang.c)