Hide class type in header
Asked Answered
S

2

1

I'm not sure if this is even possible, but here goes:

I have a library whose interface is, at best, complex. Unfortunately, not only is it a 3rd-party library (and far too big to rewrite), I'm using a few other libraries that are dependent on it. So that interface has to stay how it is.

To solve that, I'm trying to essentially wrap the interface and bundle all the dependencies' interfaces into fewer, more logical classes. That part is going fine and works great. Most of the wrapper classes hold a pointer to an object of one of the original classes. Like so:

class Node
{
public:
    String GetName()
    {
        return this->llNode->getNodeName();
    }

private:
    OverlyComplicatedNodeClass * llNode; // low-level node
};

My only problem is the secondary point of this. Beside simplifying the interface, I'd like to remove the requirement for linking against the original headers/libraries.

That's the first difficulty. How can I wrap the classes in such a way that there's no need to include the original headers? The wrapper will be built as a shared-library (dll/so), if that makes it simpler.

The original classes are pointers and not used in any exported functions (although they are used in a few constructors).

I've toyed with a few ideas, including preprocessor stuff like:

#ifdef ACCESSLOWLEVEL
#    define LLPtr(n) n *
#else
#    define LLPtr(n) void *
#endif

Which is ugly, at best. It does what I need basically, but I'd rather a real solution that that kind of mess.

Some kind of pointer-type magic works, until I ran into a few functions that use shared pointers (some kind of custom SharedPtr<> class providing reference count) and worse yet, a few class-specific shared pointers derived from the basic SharedPtr class (NodePtr, for example).

Is it at all possible to wrap the original library in such a way as to require only my headers to be included in order to link to my dynamic library? No need to link to the original library or call functions from it, just mine. Only problem I'm running into are the types/classes that are used.

The question might not be terribly clear. I can try to clean it up and add more code samples if it helps. I'm not really worried about any performance overhead or anything of this method, just trying to make it work first (premature optimization and all that).

Solitta answered 19/8, 2010 at 19:35 Comment(4)
To do that, you typically need to wrap everything up, and implement it in the source files.Denise
I am wrapping everything, and exporting functions that do what I need to access. Not sure what you mean, exactly. Wrap the shared pointer classes and such as well? That works, except I still have the occasional pointer-to-x in the headers. All the implementation is in the source.Solitta
isn't this related to the PIMPL (private implementation) idiom ?Pyne
Also see Hide class type in header, How can I hide a class in C++?, Hiding a C++ class in a header without using the unnamed namespaceMandarin
B
4

Use the Pimpl (pointer to implementation) idiom. As described, OverlyComplicatedNodeClass is an implementation detail as far as the users of your library are concerned. They should not have to know the structure of this class, or even it's name.

When you use the Pimpl idiom, you replace the OverlyComplicatedNodeClass pointer in your class with a pointer to void. Only you the library writer needs to know that the void* is actually a OverlyComplicatedNodeClass*. So your class declaration becomes:

class Node
{
public:
    String GetName();

private:
    void * impl; 
};

In your library's implementation, initialize impl with a pointer to the class that does the real work:

my_lib.cpp

Node::Node()
: impl(new OverlyComplicatedNodeClass)
{
// ...
};

...and users of your library need never know that OverlyComplicatedNodeClass exists.

There's one potential drawback to this approach. All the code which uses the impl class must be implemented in your library. None if it can be inline. Whether this is a drawback depends very much on your application, so judge for yourself.

In the case of your class, you did have GetName()'s implementation in the header. That must be moved to the library, as with all other code that uses the impl pointer.

Bead answered 19/8, 2010 at 21:9 Comment(4)
The inlining of GetName() was purely for readability, showing that I need to access members/methods of the wrapped class. My headers are clean of implementation, so that's not a problem. It's all going in a shared library anyway, so that provides another layer of insulation. My only question as to this method is how to handle haredPtr<> and more importantly the specific NodePtr and such? I'm not entirely familiar with the details of pimpl, but some examples show using another class located purely in the code file to manage things like that. Would that be a viable solution?Solitta
Some uses of the pimpl idiom can get quite complex, and it can be hard to tell between what is pimpl and what is other stuff. Using pimpl in the implementation file is quite simply just a matter of doing a reinterpret_cast<MyImplClass*>(impl) whenever you need to use the implementation class. Often this is abstracted away in to a helper function local to the library, such as MyImplClass* get_impl(void* v) { return reinterpret_cast<MyImplClass*>(v); }Bead
@peachykeen: If I didn't answer your question, please let me know & I'll try again.Bead
No worries, this is working out fine. Bit of a pain to write, but it works pretty well so far. Thanks.Solitta
S
0

Essentially, you need a separate set of headers for each use. One that you use to build your DLL and one with only the exported interfaces, and no mention at all of the encapsulated objects. Your example would look like:

class Node
{
public:
    String GetName();
};

You can use preprocessor statements to get both versions in the same physical file if you don't mind the mess.

Sastruga answered 19/8, 2010 at 20:15 Comment(2)
I was thinking about this. I'm thinking using an #ifdef should be able to handle it. Will there be any object-size related side-effects? For example, if my class N contains private members: a ptr to a T and a SharedPtr<Q>; will there be any issues using it from a program where those parts are preprocessor-hidden?Solitta
Object size only becomes a factor when doing static initialization, which I'm not sure even makes sense on a class that is dynamically linked. If you need static objects, John's Pimpl idea is the way to go.Sastruga

© 2022 - 2024 — McMap. All rights reserved.