4

I'm working with a legacy C library interface (to C++) that exposes opaque pointers as

typedef void * OpaqueObject

In the library:

OpaqueObject CreateObject()
{
   return new OurCppLibrary::Object();
}

This of course provides absolutely no type safety for clients of this library. Should changing the typedef from a void pointer to structure pointer work exactly the same, but provide a small amount type safety?

typedef struct OpaqueObjectInternal_ *OpaqueObject 
// OpaqueObjectInternal_ is NEVER defined anywhere in client or library code

Are there any alignment issues or other gotchas that I have to worry about now that I am explicitly pointing to a structure, even though I'm really not pointing to one?

Cat Zimmermann
  • 1,422
  • 2
  • 21
  • 38
  • 3
    There is no operator called `new` in **C** !! – Mahesh Mar 14 '11 at 21:16
  • 3
    @Mahesh: I think the OP is wrapping a C library in C++; the `new` is taking place in C++ (inferred from the `OurCppLibrary` qualifier) – fbrereto Mar 14 '11 at 21:19
  • 2
    `void *` makes perfect sense as an opaque object - your end of things doesn't get to mess with the object, and they know what to do with it. If anything, making it a `void *` makes it safer, so long as you just use it with their API as described. – James Mar 14 '11 at 21:40
  • 1
    @James: I don't understand, how does removing type information make it safer? Consider this example: `void some_api_func(void*); int* i = ...; some_api_func(i); // oops!`. That can't happen with type safety. – GManNickG Mar 14 '11 at 21:52
  • @GMan - You used the API incorrectly, and it is quite easy to spot. As long as you call their functions using their 'opaque' pointers, you're fine. If they were to expose the type, you might be tempted to manipulate the objects they hand back when you were never meant to. If it is a `void *`, no such illusions hold. – James Mar 14 '11 at 22:09
  • I'm wrapping a C++ implementation with a C interface. – Cat Zimmermann Mar 14 '11 at 22:09
  • 1
    @James: I would really like the compiler to catch as many errors as possible. If you are given an ObjectInternal_ *, what are you going to do with it? I think it's far more likely that you'll pass the wrong object than do something with a type that has no internals or methods that use it. – Cat Zimmermann Mar 14 '11 at 22:13
  • @Cat: Wait, you are wrapping C++ with C or vice versa? If you are wrapping their library, you might be able to get away with passing around an `OpaqueObject` struct by value to your functions that contains the pointer within, and forwards to the method call. Then the compiler should catch any type errors. – James Mar 14 '11 at 22:15
  • @James: "You used the API incorrectly..." Indeed, and when do you think the best time to find out is? At compile-time through type-checking, or at run-time (the day before release) through undefined behavior and a crash? And I don't understand the point of your second rejection: the entire purpose of the opaque pointer is to *not* expose the type definition. – GManNickG Mar 14 '11 at 22:20
  • @GMan: So not quite safer, but I've used numerous libraries with `void *` opaque objects and never passed in an incorrect type. Perhaps it is my experience clouding my view of the safety level, but I saw that as somewhat of a non-issue as a good variable naming will usually prevent passing in the wrong pointer. Regardless, see my second comment above for what I think is the best solution. – James Mar 14 '11 at 22:35
  • @James: I'm wrapping C++ with C. Both the C wrapping and the C++ are legacy at this point, and no one on my team is willing to do much modification as there's no unit testing. Right now I just want to do almost zero work to ensure type safety; these typedefs are generated from IDL so it's trivial. – Cat Zimmermann Mar 14 '11 at 22:36
  • @James: Ok, that's good for *you*, but the rest of us would prefer to properly utilize the compiler and language-features available to us to write correct code better. (I never quite understood that, given the choice between: "if you fail it's because you suck; improve" and "while some may get by scratch-free, people make mistakes and we can protect against that *for free*", people chose the former. I thought we were past the age of elitism. ;) ) – GManNickG Mar 14 '11 at 22:53
  • @GMan: Elitism... Elitism never changes. :) Besides, 'If you fail it's because you suck' has been pretty much what I've been told about C++ programming since I started... :( – James Mar 15 '11 at 14:53

3 Answers3

8

There are no gotcha's; that form is preferred exactly because of type safety.

No, alignment is not an issue here. The pointer itself has a known alignment, and the alignment of the object it will point at is only of concern to the library implementation, not the user.

GManNickG
  • 494,350
  • 52
  • 494
  • 543
2

Actually, a C++ class is also a C struct. So you could simply do this:

struct Object;
struct Object* Object_Create(void);

And, the C wrapper in the library thus:

struct Object* Object_Create(void)
{
    return new Object;
}

A method call might look like this:

uint32_t Object_DoSomething( struct Object* me, char * text )
{
    return me->DoSomething( text );
}

I have tried this and I see no downsides.

Robert
  • 21
  • 1
1

The first thing to consider with your suggested course is what are you communicating to the others that may have to maintain the code you are writing. Above all other things opaque objects are the C way to indicate to the user of a library that the library maintainer makes absolutely no guarantee as to the implementation of an object other than that the object can be used with the documented functions. By removing the void* you are basically announcing to the world, "I declare this implementation that was once opaque to be stable." This may not be what you want.

Secondly, IMHO you are proposing is the sort of half solution that makes no one happy, a better approach is to develop a wrapper class. This has the added benefit of allowing you to wrap the init and destroy functions that inevitably accompany C style opaque objects in the constructors and destructor of you class. This will allow you to provide resource management as well as type safety to your clients with out a whole lot more work.

ltc
  • 3,313
  • 1
  • 28
  • 26
  • 1
    I don't understand why the struct pointer isn't opaque. There is no definition of the struct that the client can access (or the implementation, for that matter.) Regarding a wrapper class, this is for a pure C consumer. – Cat Zimmermann Mar 15 '11 at 17:08
  • Looks like I got confused by the fact that the example uses C++ constructs and the title says C++. Also, I would recommend against naming the struct OpaqueObjectInternal_ both Internal and the trailing underscore are common conventions that indicate private implementations. Not reading the comments carefully enough led me to believe that this struct was the internal implementation not the opaque pointer. There is perhaps a lesson for us both here ;-) – ltc Mar 16 '11 at 06:51
  • Someone edited the title from C to C++. What would you name the struct? There is a different struct for every type that the C interface knows about. – Cat Zimmermann Mar 16 '11 at 21:48
  • I would just chop the Internal_ off. Sometimes people like to do something like typedef struct typename_opaque_t * typename_handle; but that is really up to you. – ltc Mar 17 '11 at 06:34