4

I have an opaque type in my library defined as:

typedef struct MyOpaqueType* MyType;  // easier to type for client code

I can't pass a pointer-to-const struct around using the typedef, so some functions look like:

void UsePointerToConst ( const struct MyOpaqueType * )

instead of:

void UserPointerToConst( const MyType ) // can't use, is really constant pointer

So, given this, I have two questions: Is the struct keyword in the parameter list only necessary in C? Is there a better way to do this? Should I create a typedef such as:

typedef const struct MyOpaqueType* ConstantMyType; ?
Cat Zimmermann
  • 1,422
  • 2
  • 21
  • 38
  • 4
    Win32 does exactly that: `LPSTR` (pointer to null-terminated string) vs `LPCSTR` (pointer to `const` null-terminated string), etc etc. – Jon Jun 29 '11 at 19:39
  • The second typedef is a reasonable solution to the problem. – Bill Jun 29 '11 at 20:42
  • 1
    The fact that Win32 does it this way is no reason for anyone to copy its bad habits. – Fred Foo Jun 29 '11 at 20:54

3 Answers3

5

Is the struct keyword in the parameter list only necessary in C?

Yes. See Jens Gustedt's answer.

Is there a better way to do this?

Just typedef the struct, not the pointer. This is better because

  • you only need one typedef instead of one for each of {MyOpaqueType, MyOpaqueType *, MyOpaqueType const *, MyOpaqueType *const and MyOpaqueType const *const} and all variants involving restrict (which doesn't exist in C++),
  • it's clear to the user that pointer semantics apply, i.e., passing the datatype around is really a matter of pointer copying (no performance worries), users are less likely to forget cleaning up after use, C++ users may use smart pointers, and
  • it's a common C convention (think FILE *).

There's also no danger; when someone forgets the *, they get a compiler error.

Fred Foo
  • 355,277
  • 75
  • 744
  • 836
  • 4
    @Cat It exists, but is incomplete. You can still typedef it. – Alan Stokes Jun 29 '11 at 19:46
  • Yes, but hiding the notion that this opaque handle is a pointer is something I'd like to keep. typedef struct MyOpaqueType* MyType is a library idiom that this code has used for years. I might not understand what you're getting at, though. – Cat Zimmermann Jun 29 '11 at 20:31
  • @Cat It's a common C idiom. C++ is a bit different - the struct keyword introduces a new type name, for example. You could use two typedefs (for const and non-const), but idiomatic C++ would probably do it as I suggest in my answer. – Alan Stokes Jun 29 '11 at 20:46
  • @Cat: I was in a hurry when I typed this answer. Expanded it with why pointer `typedef`'s are a bad idea for your problem. – Fred Foo Jun 29 '11 at 20:47
  • +1 for recommending against hiding that the target of the `typedef` is a pointer. – bta Jun 29 '11 at 20:58
  • I like the reminder about the information conveyed when using obvious pointer semantics. Thanks. – Cat Zimmermann Jun 29 '11 at 22:04
3

In C++ a typedef of the same name as a struct is assumed as long as there is no other identifier with that name. So something like a function stat that receives a struct stat* as an argument:

int stat(const char *path, struct stat *buf);

is even allowed in C++. (This is a real world example.)

So you are always better of with forward declarations like

typedef struct toto toto;

which reserves the token toto in the identifier and in the struct name space. Then you can have your function interface declared for C and C++. But don't forget the extern "C" if you want to access it from C, too.

See also: this answer on SO and struct tags are not identifiers in C++.

Community
  • 1
  • 1
Jens Gustedt
  • 76,821
  • 6
  • 102
  • 177
  • 1
    To be precise, the identifiers of user defined types and the rest of identifiers are kept separate in C++ in the same way as in C, **but**, when looking up an identifier in a scope, if the identifier is not located in the global identifier space, then it is also searched in the user defined types identifier space... kind of subtle difference. – David Rodríguez - dribeas Jun 29 '11 at 21:28
  • @David, yes, it even allows more absurd things as first using the bare word as a referring to the `struct` and then defining a variable afterwards. C++ is a very context sensitive language, but we knew that already, don't we. – Jens Gustedt Jun 29 '11 at 21:50
2

You don't need the typedef at all in C++. Just use a forward declaration:

struct MyType;

Then pass around MyType const *, MyType *, MyType const & etc as and when required.

Alan Stokes
  • 18,815
  • 3
  • 45
  • 64
  • I don't want to ever pass around anything but a pointer the structure, because the structure does not exist, and just exists for type safety. – Cat Zimmermann Jun 29 '11 at 20:36
  • 1
    @Cat It most definitely exists; you declared it so it exists. You don't ever have to define it for the above to work. Try it! – Alan Stokes Jun 29 '11 at 20:42
  • Even if you never define what that structure looks like, you can still use a forward declaration like this to allow you to create pointers to a structure with that name. Just don't try to de-reference them if you haven't defined the structure... – bta Jun 29 '11 at 21:02
  • Oh, I see what you mean. Sorry, I read your comment too quickly. Won't C code parameters have to be written as PublicFunction(struct MyType* t)? I think PF(MyType t) is easier to read. – Cat Zimmermann Jun 29 '11 at 21:57
  • 1
    @Cat Write `typedef struct MyType MyType;` if you need it to work in both C and C++. `PF(MyType * t)` is both easy to read and explicit about what is happening. – Alan Stokes Jun 29 '11 at 22:01