How to make an adapter class to support both const and non-const underlying data appropriately?
Concrete Example
RigidBody
is a class describing physic property of object.
Here is its very simplified version (1D):-
class RigidBody{
float position=1;
public: float getPosition()const{ return position;}
public: void setPosition(float ppos){ position=ppos;}
};
Adapter
encapsulates RigidBody
.
It provides a-little-distorted functionality to get/set position
:-
class Adapter{
public: RigidBody* rigid; int offset=2;
public: float getPosition(){
return rigid->getPosition()+offset; //distort
}
public: void setPosition(float ppos){
return rigid->setPosition(ppos-offset); //distort
}
};
I can set position of RigidBody
indirectly by using Adapter
:-
int main() {
RigidBody rigid;
Adapter adapter; //Edit: In real life, this type is a parameter of many function
adapter.rigid=&rigid;
adapter.setPosition(5);
std::cout<<adapter.getPosition();//print 5
return 0;
}
Everything works (demo).
Objective
I want create a new function that receives const
RigidBody* rigid
.
I should be able to read from it (e.g. getPosition()
) by using adapter.
However, I don't really know how to do it elegantly.
void test(const RigidBody* rigid){
Adapter adapter2;
//adapter2.rigid=rigid; //not work, how to make it work?
//adapter2.setPosition(5); //should not work
//adapter2.getPosition(); //should work
}
My poor solutions
Solution A1 (2 adapters + 1 widget)
Create a widget :-
class AdapterWidget{
public: static Adapter createAdapter(RigidBody* a);
public: static AdapterConst createAdapter(const RigidBody* a);
};
AdapterConst
can only getPosition()
, while AdapterConst
can both get and set.
I can use it like :-
void test(const RigidBody* rigid){
auto adapter=AdapterWidget::createAdapter(rigid);
It is easy to use.
Disadvantage: Code of AdapterConst
and Adapter
will be very duplicated.
Solution A2 (+inheritance)
It is an improvement of the previous solution.
Let Adapter
(has setPosition()
) derive from AdapterConst
(has getPosition()
).
Disadvantage: It is not concise. I use 2 classes for the single task!
This might seem to be trivial, but in a bigger code-base, it is not fun at all.
Specifically, location of getPosition()
will be far-away from setPosition()
, e.g in different files.
This causes maintainability problem.
Solution B (template)
Create a template class. There are many ways e.g. :-
Adapter<T =RigidBody OR const RigidBody >
Adapter<bool=true is const OR false is non-const >
Disadvantage: In every ways, it is inelegant. It is an overkill. (?)
I will suffer from disadvantage of template e.g. everything in header.
Solution C1 (const_cast)
I am trying to avoid it. It is evil.
class Adapter{
public: RigidBody* rigid;
void setUnderlying(const RigidBody* r){
rigid=const_cast< RigidBody*>(r);
}
....
};
Solution C2 (+manual assert)
I can add some assertion manually.
It just emphasizes how much it is unprofessional :-
bool isConst;
void setUnderlying(const RigidBody* r){
...
isConst=true;
}
void setUnderlying(RigidBody* r){
...
isConst=false;
}
void setPosition(float a){
if(isConst){ /*throw some exception*/ }
....
}
Solution D (run away)
- Lazy : change from
test(
const
RigidBody* rigid)
totest(RigidBody* rigid)
. - Crazy : change
RigidBody::setPosition()
to beconst
.
In either way, my program will not be const
-correct any more,
but a single Adapter
class would be enough.
Question
Do I really have to do one of these things wherever I encounter the const/non-const pattern?
Please provide a pretty solution. (no full code is required, but I don't mind)
Sorry for the long post.
Edit: In real life, Adapter
is a parameter for many function.
It is passed around like a toy.
Most of such functions don't have knowledge about RigidBody
, so it is not quite suitable to change from a bundle calling someFunction(adapter)
to someFunction(offset,rigidbody)
.