views:

102

answers:

2

I've seen both of the following two styles of declaring opaque types in C APIs. Is there any clear advantage to using one style over the other?

Option 1

// foo.h
typedef struct foo * fooRef;
void doStuff(fooRef f);

// foo.c
struct foo {
    int x;
    int y;
};

Option 2

// foo.h
typedef struct _foo foo;
void doStuff(foo *f);

// foo.c
struct _foo {
    int x;
    int y;
};
+6  A: 

My vote is for the third option that mouviciel posted then deleted:

I have seen a third way:

// foo.h
struct foo;
void doStuff(struct foo *f);

// foo.c
struct foo {
    int x;
    int y;
};

If you really can't stand typing the struct keyword, typedef struct foo foo; (note: get rid of the useless and problematic underscore) is acceptable. But whatever you do, never use typedef to define names for pointer types. It hides the extremely important piece of information that variables of this type reference an object which could be modified whenever you pass them to functions, and it makes dealing with differently-qualified (for instance, const-qualified) versions of the pointer a major pain.

R..
+1 for don't hide pointers behind typedefs! (Or, even worse, arrays.)
Thomas Padron-McCarthy
'Never' is rather strong here: the whole point of opaque types is to hide the implementation from users of your api, making changes to the former independant of the latter, and providing a measure of safety by restricting direct modifications by the user; I see nothing wrong with aliasing pointer types or hiding qualifiers in such cases (ie if they are implementation details)
Christoph
@Christoph: One problem with encapsulating e.g. `const` into the typedef is if you have a situation where you want to express that some API functions modify the object, and others don't. You could have `typedef struct foo *foo; typedef struct const foo *const_foo;`, but that's grim!
Oli Charlesworth
Whether a type is a pointer or not is **not an implementation detail**. It's fundamental to the semantics of any operation in which you might use the type. This is one 'never' I stand by completely.
R..
@R..: think about mutable vs immutable string implementations or non-compacting vs compacting collectors; the former is an example where `const` is an implementation detail, the latter where you need an additional level of indirection in the compacting case; the api may very well be agnostic to these details, but only if opaque types are used to hide qualifiers/pointers
Christoph
A type with a builtin `const` qualifier is **not valid** for immutable strings (or any allocated object) because your implementation of the object cannot `free` a `const`-qualified pointer (`free` takes a non-`const`-qualified `void *`, for good reason). This is not a technicality but a matter of violating the semantics of `const`. Sure you can cast the `const` away in your `immutable_string_free` function, but now we're getting into the territory of dirty hacks. **Any** opaque object allocation function should always return `footype *`, and the function to free should take `footype *`.
R..
@R..: I agree that casting away `const` is generally not advisable and is - as far as I know - actually not mentioned in the C standard as a possible conversion for pointer types (you can only go from non-qualified to qualified); however, allocated objects which are never modified during their lifetime after initialization are common enough, and I don't really see the benefit of keeping a non-qualified version of the pointer around for the sole purpose of freeing the object; casting to non-`const` may be a hack, but I don't know of any other feasible workaround
Christoph
@Cristoph: The solution is not to use a workaround but for the "owner" of the object to store the correct, non-constant pointer type. If the object is immutable, the only place the non-`const`-qualified version of the pointer should be used is in the return value of the allocation function(s) and the argument of the deallocation function(s). All other functions should take `const`-qualified pointers. This is one reason why making `const` (and the pointer) a built-in part of the type definition is a bad idea.
R..
@R..: see http://stackoverflow.com/questions/3999966/const-correctness-and-immutable-allocated-objects
Christoph
A: 

bar(const fooRef) declares an immutable address as argument.
bar(const foo *) declares an address of an immutable foo as argument.
For this reason, I tend to prefer option 2. I.e., the presented interface type is one where cv-ness can be specified at each level of indirection. Of course one can sidestep the option 1 library writer and just use foo, opening yourself to all sorts of horror when the library writer changes the implementation. (I.e., the option 1 library writer only perceives that fooRef is part of the invariant interface and that foo can come, go, be altered, whatever. The option 2 library writer perceives that foo is part of the invariant interface.)

I'm more surprised that no one's suggested combined typedef/struct constructions.
typedef struct { ... } foo;

Eric Towers
Regarding your last sentence, these constructions do not admit opaque types. If you use them, you're exposing the definition of the structure in your header for the calling application to abuse.
R..