Adrian Lis
Adrian Lis

Reputation: 657

Interfaces, hiding concrete implementation details in C++

I have a question regarding hidinging interface details in C++ libraries. The problem is ilustrated with the following example:

Let's say w have a class called ISystem which exposes methods like Init, Run, Tick, Shutdown, GetXXXSubSystem. Where X are pointers various interfaces like: ISoundSystem, IInputSystem

We also have concrete implementations of ISystem like: Win32System, OSXSystem etc.

These specific implementations use a pimpl idiom to hide internals and for example Win32System instantiates Win32RawInputSystem as input system manager.

All such managers do have their own Init, Run, Tick and Shutdown methods which are not part of the interface (only concrete implementation) and these are run and managed by the concrete system implementation.

The user calling GetXXXSubSystem gets interface pointer without those methods (Init etc..) but still he could cast the pointer he gets to concrete implementation and trigger methods like Init Run Tick etc. which he shouldn't have access to.

The question is, is it possible to hide the concrete class somehow? I tried to make those methods protected in the concrete implementations and template the class on type which would eventually be friend but this appears to be prohobited and existing hacks do not work with VC11.

The only way I can think of right know is to transfer the concrete implementation declaration from header into the cpp file of Win32System class but I see ahuge drawback of doing this (even not sure if this would work), because this way each subsystem would have to be also part of this cpp file and it would become a maintainability nightmare.

Another solution I am thinking about is using factory method like

(RawInput.h) 
IInputSystem* CreateRawInputSystem();

(RawInput.cpp)
class RawInput : public IInputSystem {}; ...

and move definition of the class to cpp file but then, how I would acces this type from other parts of my library (ie in Win32System impl)?

Is it possible to include .cpp files form other .cpp files?

Thanks in advance for any tips.

Upvotes: 1

Views: 1575

Answers (1)

If you're developing a library here, then you can simply choose not to export the header files of the concrete classes that you do not want to expose. You cannot cast to a class of which you do not have a definition.

Example :
MyProjectFolder/Src/Export/ISystem.h

#ifndef ISYSTEM_H
#define ISYSTEM_H

#include "IInputSystem.h"

class ISystem
{
public:
    virtual ~ISystem() {};
    virtual void Run()=0;
    virtual IInputSystem* GetInputSystem()=0;
};

#endif

MyProjectFolder/Src/Export/IInputSystem.h

#ifndef IINPUTSYSTEM_H
#define IINPUTSYSTEM_H

class IInputSystem
{
public:
    virtual ~IInputSystem() {};
    virtual void Foo()=0;
    virtual void Bar()=0;
};

#endif

MyProjectFolder/Src/Export/Win32System.h

#ifndef WIN32SYSTEM_H
#define WIN32SYSTEM_H

#include "ISystem.h"

class Win32System : public ISystem
{
public:
    Win32System();
    virtual void Run();
    virtual IInputSystem* GetInputSystem();

private:
    struct impl;
    impl* m_pImpl;
};

#endif

MyProjectFolder/Src/Win32RawInputSystem.h

#ifndef WIN32RAWINPUTSYSTEM_H
#define WIN32RAWINPUTSYSTEM_H

#include "IInputSystem.h"

class Win32RawInputSystem : public IInputSystem
{
public:
    virtual void Foo();
    virtual void Bar();

    virtual void Run(); // you do not want to expose that function
};

#endif

MyProjectFolder/Src/Win32System.cpp

#include "Win32System.h"

#include "Win32RawInputSystem.h"

struct Win32System::impl
{
    Win32RawInputSystem inputSys;
};

Win32System::Win32System()
 : m_pImpl(new impl)
{
}

void Win32System::Run()
{ // run run run
}

IInputSystem* Win32System::GetInputSystem()
{
    return &m_pImpl->inputSys;
}

So when building your project its include search path is not only Src/ but also Src/Export/. From within your library project you can use all classes, including Win32RawInputSystem. When deploying your library you only give away those headers that reside in the Src/Export/ folder. Clients can still use the library, but they can never cast IInputSystem* to Win32RawInputSystem* because they do not have that header. Therefore the users of that library can invoke Foo() and Bar() on the IInputSystem*, but they'll never be able to invoke Run().

Upvotes: 1

Related Questions