4

A common way to implement "PIMPL" in C is to do this:

typedef struct _Opaque Opaque;

Opaque* createOpaque();
void doSomething(Opaque *opaque);

Or:

typedef struct _Opaque* Opaque;

Opaque createOpaque();
void doSomething(Opaque opaque);

The first requires that the caller treates Opaque as a pointer, the second requires that the caller treates Opaque as a value.

The asterisks don't make the code any more readable, but are there any hidden dangers of typedefing it away? Given the choice between the two styles, which should be preferred?

1
  • 1
    It's usually a mistake to hide pointers behind typedefs; if you do, you have to guarantee that your API hides all aspects of that type's pointer-ness from the consumer.
    – John Bode
    Commented Apr 3, 2017 at 21:25

1 Answer 1

8

It is a matter of taste, but I prefer a lot the first style in C: having opaque struct with explicit and systematic pointer to them. BTW, GTK is systematically following such a style. And so does standard <stdio.h> with its FILE.

C is a low level language, and the programmer is expecting to understand low level details. He usually expects to know (or to intuit) the size of a given type. He knows that passing a pointer is really fast. And the programmer should know that it is a pointer. Otherwise, he might be tempted (after several layers of software) to "pass by reference", that it to pass a pointer to, that opaque thing.

In C you can sometimes pass a struct by value, and in effect all the data is copied when you do that. This is sometimes good, when the struct is small (e.g. struct Point_st { int x, y;} for cartesian coordinates...). But in other cases, it is inefficient (for example, you probably don't want to pass an entire struct stat from stat(2) by value).

At last, on some ABIs (notably x86-64 for Linux) there might be a special calling convention for small struct. On x86-64 a struct with two pointer fields is returned thru registers (not using any memory zone for that).

In other languages (Go notably, and probably C++) the situation is different, and the second style is preferable (but C++ has const Opaque& to pass by "constant reference", in effect "hiding" a pointer).

The asterisks don't make the code any more readable,

That is your opinion, and I disagree with it. They make IMHO the code a lot more readable.

6
  • Interesting answer. However, in the second case, the compiler will never use the small struct calling convention for this typedef: in the code generation, the typedef is not treated as a new struct containing a single pointer, but still as a stuct*. The sam applies for c++
    – Christophe
    Commented Apr 1, 2017 at 7:23
  • But if a programmer knows that something is a pointer, he wont be tempted to pass its address to "speedup" things. Commented Apr 1, 2017 at 7:50
  • +1 And speaking from a C++ perspective: No, even there I prefer the first version. To quote Python: Explicit is better than implicit. Although in C++ I consider an opaque pointer to be a code smell. Usually there are better language constructs available to achieve the same thing.
    – besc
    Commented Apr 1, 2017 at 7:58
  • Don't misunderstand me: I also recommend the first version, but because it's self explaining (e.g. Clean code practice). Having experienced the second form once in a larger app (at a time the struct were not allowed to be passed by value), i can only confirm that it ends up to be very confusing. My point was just to draw attention that the compiler code generation argument was not valid. the programmer trying to pass by reference is on contrary an excellent additional argument.
    – Christophe
    Commented Apr 1, 2017 at 8:28
  • 1
    @besc see my comment above for the c part. In c++ i'd use a unique_ptr together with the bridge pattern. However, pimpl is not necessarily a bad practice: it serves a different purpose, acting as a "compilation firewall" that enforces a complete encapsulation thanks to the incomplete type. As a by-product, this can lead to huge acceleration in compile time for larger projects (by avoiding the processing of lots of nested includes). Sometimes it's even required, for example if the header is the API for a DLL delivered without source code, and some internals are simply not meant to be known.
    – Christophe
    Commented Apr 1, 2017 at 8:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.