Reputation: 2832
I have an object of class Level
that has several subsystems (child objects) it uses to manage its state (e.g. EntityManager
, InputManager
, Physics
). I want to expose some of the methods of those subsystems in the external interface of Level
.
Here is one solution:
uint32_t Level::CreateEntity()
{
return entityManager.CreateEntity();
}
Entity& Level::GetEntity(uint32_t entityId)
{
return entityManager.GetEntity(entityId);
}
uint16_t Level::CreateInputState()
{
return inputManager.CreateInputState();
}
void Level::AttachInputState(uint32_t entityId, uint16_t inputStateId)
{
inputManager.AttachInputState(entityId, inputStateId);
}
InputState& Level::GetInputState(uint16_t inputStateId)
{
return inputManager.GetInputState(inputStateId);
}
This solution requires me to duplicate the method declarations inside the Level
class and write one-line calls that redirect the control to the subsystem. In projects I have worked on in the past, this has been troublesome to manage.
Another solution is to expose the subsystems in the public interface. Preferably this can be avoided since it's not important to objects outside of Level
which subsystem the calls get forwarded to.
Is there a design that could more elegantly handle this problem?
Upvotes: 0
Views: 219
Reputation: 16054
Another way to aggregate features is via private inheritance. Then you can turn some of the inherited private methods into public methods with a using
directive. For instance:
#include <iostream>
class feature_A
{
public:
void func_A1() { std::cout << "A1" << std::endl; }
void func_A2() { std::cout << "A2" << std::endl; }
};
class feature_B
{
public:
void func_B1() { std::cout << "B1" << std::endl; }
void func_B2() { std::cout << "B2" << std::endl; }
};
class compound : private feature_A, private feature_B
{
public:
// Provide these functions as-is.
using feature_A::func_A1;
using feature_B::func_B1;
// Combine these two functions.
void func_C2() { func_A2(); func_B2(); }
};
int main()
{
compound c;
c.func_A1();
c.func_B1();
c.func_C2();
// c.func_A2(); // error: ‘void feature_A::func_A2()’ is inaccessible
}
One limitation of those using
declarations is that they with a name. If you have multiple overloads of the same function, you cannot select only one to be made public. Similarly for template method: you cannot use using
to select only one specialization.
Upvotes: 1
Reputation: 9547
As requested by the OP I will post a solution that I suggested in the comments. I am fairly sure, that there is a better one with templates in the new C++ standard but anyway I will post the preprocessor solution which is ugly and should not be used!
#define FUNCTION_DECLARATION(RETURNTYPE, FUNCTIONNAME, ...) \
RETURNTYPE FUNCTIONNAME(__VA_ARGS__)
This would be used in the class declaration as:
class Level {
FUNCTION_DECLARATION(uint32_t, CreateEntity);
FUNCTION_DECLARATION(Entity&, GetEntity, uint32_t);
};
The definition would look like:
#define FUNCTION_DEFINITION(RETURNTYPE, PROPERTY, FUNCTIONNAME, ...) \
RETURNTYPE Level::FUNCTIONNAME(__VA_ARGS__) \
{ \
return PROPERTY.FUNCTIONNAME(__VA_ARGS__); \
} \
And now the very ugly usage to make this work:
FUNCTION_DEFINITION(Entity&, entityManager, GetEntity, uint32_t(entityId))
I can not guarantee that this will work for any type also I have not tested most of the code. As you can see this "hack" with the input will only work for plain types and not references or pointers. On classes it will trigger the copy constructor!
Of course this can be improved by replacing the VA_ARGS with a variable for the type and the variablename for each argument of the function but again this is very tedious and one would have to write a template for each number of arguments that should be used. This will in result yield nearly as much code as before.
So let me state again: Stick with templates they are by far better than this! It is just that I do not know how to do it with them. So please if there is anyone out there who knows how to do it with templates, instead of bashing me for this ** code write the beautiful solution everyone here is aching for.
Upvotes: 1
Reputation: 75150
The only think I know of that would make the interface more elegant is to have another object contain private
ly an EntityManager
, Physics
, and so on, and have it public
ly expose functions that call each of the functions you want clients to be able to call on the underlying objects. Make Level
have an instance of those proxy objects in the public interface, and optionally make those proxies non-copyable, non-moveable, etc.
This would not reduce the amount of work for you (it would in fact increase it), but it would make the interface more organised and easier to use.
Example:
class Physics {
public:
T gravity() { ... } // we want them to be able to call this
T2 nope() { ... } // but not this
};
class LevelPhysics {
public:
T gravity() { return phys.gravity(); }
private:
LevelPhysics(Physics& phys) : phys(phys) { }
Physics& phys;
friend class Level;
};
class Level {
public:
LevelPhysics GetPhysics() { return LevelPhysics(phys); }
private:
Physics phys;
};
Then you can use it like
const LevelPhysics& phys = lvl.GetPhysics();
phys.gravity();
// but you can't use Physics::nope
Unfortunately there is no language feature to "make these for you". You can't get around doing some coding yourself.
Alternatively, if you know all of the classes that need to access the secret members of Physics
, you can simply do this:
class Physics {
public:
T gravity() { ... }
private:
T2 nope() { ... }
friend class Level;
};
class Level {
public:
Physics physics;
};
Then
lvl.physics.gravity();
// but can't do lvl.physics.nope();, only Level can
Upvotes: 0