by InexorableTash » Jan 17, 2003 @ 6:43pm
There are several design patterns for this:
- Export only an interface
This is what Microsoft's COM does. The header file for your DLL contains only abstract base classes (interfaces) such as ISoundMixer, ISoundBuffer, etc. and a factory for creating them in a function, such as CreateSoundMixer().
This is the pattern I tend to follow since I "grew up" with it. I also use IUnknown-style refcounting since smart pointers and a good leak tracker (VC provides this free) make it transparent and easy once you have the religion.
Down side - all of your objects are referenced with pointers. This clashes with GapiDraw's simpler style. You also can't allocate on the stack.
- Get a PIMPL
This design pattern - short for Private IMPLemenetation, defines a public class definition like this in the .H you distribute:
CSoundBuffer
{
public:
// constructor, methods, etc, but no storage or anything private
private:
void* _pimpl;
}
You have a private definition for the same class when you are building the DLL which is mostly identical but has one small change:
CSoundBuffer
{
public:
// constructor, methods, etc, but no storage or anything private
private:
CSoundBufferImpl* _pimpl;
}
The constructor creates a CSoundBufferImpl and saves the pointer in _pimpl. The destructor destroys it. All of your other methods (e.g. LoadWave) simply call the real implementation on the implementation class (e.g. _pimpl->LoadWave() ).
The user will just see a normal class they can instantiate with x = new CBlahBlah() or even CBlahBlah x() for stack allocations. (If you don't know about stack allocations in C++ you're really missing out!)
Down side: Maintaining two real class implementations plus two headers is heavier weight than just maintaining an interface plus a class definition plus an implementation.
...
There are other design patterns, and a lot of references on the web about them. Google search on "hiding implementation" and "design pattern".
Note that in either of these design patterns you're going to suffer a speed hit calling the methods - in the interface method you're hitting a VTable, and in the PIMPL method there's the un-optimizable second call. So you shouldn't expect callers to use these in tight loops -- but you shouldn't be making function calls in tight loops anyway. This is one reason you have Lock()-type methods that give a caller all the data they need to do the work without calling any other methods.