Object oriented design for hotel reservation system - c++

I am practicing object oriented design for an upcoming interview. My question is about the design for a hotel reservation system:
- The system should be able to return an open room of a specific type or return all the open rooms in the hotel.
- There are many types of rooms in hotel like regular, luxury, celebrity and so on.
So far I have come up with following classes:
Class Room{
//Information about room
virtual string getSpecifications(Room *room){};
}
Class regularRoom: public Room{
//get specifications for regular room
}
Class luxuryRoom: public Room{
//get specifications for regular room
}
//Similarly create as many specialized rooms as you want
Class hotel{
vector<Room *>openRooms; //These are all the open rooms (type casted to Room type pointer)
Public:
Room search(Room *aRoom){ //Search room of a specific type
for(int i=0;i<openRooms.size();i++){
if(typeid(*aRoom)==typeid(*openRooms[i])) return *openRooms[i];
}
}
vector<Room> allOpenRooms(){//Return all open rooms
...
}
}
I am confused about the implementation of hotel.search() method where I am checking the type (which I believe should be handled by polymorphism in some way). Is there a better way of designing this system so that the search and allOpenRooms methods can be implemented without explicitly checking the type of the objects?

Going through the sub-class objects asking what type they are isn't really a good illustration of o-o design. You really need something you want to do to all rooms without being aware of what type each one is. For example print out the daily room menu for the room (which might be different for different types).
Deliberately looking for the sub-class object's type, while not being wrong, is not great o-o style. If you just want to do that, as the other respondents have said, just have "rooms" with a set of properties.

You could always let a room carry it's real type, instead of comparing the object type:
enum RoomType
{
RegularRoom,
luxuryRoom
};
class Room{
public:
explicit Room(RoomType room_type) : room_type_(room_type) { }
virtual ~Room(){}
RoomType getRoomType() const { return room_type_; }
private:
RoomType room_type_; // carries room type
};
class regularRoom: public Room{
public:
regularRoom() : Room(RegularRoom){ }
};
Room search(Room *aRoom)
{
//Search room of a specific type
for(size_t i=0;i<openRooms.size();i++)
{
if (aRoom->getRoomType() == RegularRoom) // <<-- compare room type
{
// do something
}
}
};

Do the different types of rooms have different behavior? From
the description you give, this is not a case where inheritance
should be used. Each room simply has an attribute, type, which
is, in its simplest form, simply an enum.

The simplest way is to have a Room type enumeration as #billz suggest you. The problem with tis way is that you must not forget to add a value to the enumeration and use it once every time you add a new type of Room to the system. You have to be sure you use the enum values only once, one time per class.
But, on the other hand, inheritance bassed dessigns only have sense if the types of the hierarchy shares a common behaviour. In other words, you want to use them in the same way, regardless of its type. IMPO, an OO/inheritance dessign is not the better way to do this.
The freak and scalable way I do this type of things is through typelists.
Normally, you have different search criteria for every type in your system. And, in many cases, the results of this search are not the same for different types of your system (Is not the ssame to search a luxury room and to search a normal room, you could have different search criteria and/or want different search results data).
For this prupose, the system has three typelists: One containing the data types, one containing the search criteria types, and one containing the search results types:
using system_data_types = type_list<NormalRoom,LuxuryRoom>;
using search_criteria_types = type_list<NormalRoomsCriteria,LuxuryRoommsCriteria>;
using search_results_types = type_list<NormalRoomSearchResults,LuxuryRoomSearchResults>;
Note that type_lists are sorted in the same manner. This is important, as I show below.
So the implementation of the search engine is:
class SearchEngine
{
private:
std::vector<VectorWrapper*> _data_lists; //A vector containing each system data type in its own vector. (One vector for NormalRoom, one for LuxuryRoom, etc)
//This function returns the vector that stores the system data type passed.
template<typename T>
std::vector<T>& _get_vector() {...} //Implementation explained below.
public:
SearchEngine() {...}//Explanation below.
~SearchEngine() {...}//Explanation below.
//This function adds an instance of a system data type to the "database".
template<typename T>
void addData(const T& data) { _get_vector<T>().push_back( data ); }
//The magic starts here:
template<typename SEARCH_CRITERIA_TYPE>//This template parameter is deduced by the compiler through the function parameter, so you can ommit it.
typename search_results_types::type_at<search_criteria_types::index_of<SEARCH_CRITERIA_TYPE>> //Return value (The search result that corresponds to the passed criteria. THIS IS THE REASON BECAUSE THE TYPELISTS MUST BE SORTED IN THE SAME ORDER.
search( const SEARCH_CRITERIA_TYPE& criteria)
{
using system_data_type = system_data_types::type_at<search_criteria_types::index_of<SEARCH_CRITERIA_TYPE>>; //The type of the data to be searched.
std::vector<system_data_type>& data = _get_vector<system_data_type>(); //A reference to the vector where that type of data is stored.
//blah, blah, blah (Search along the vector using the criteria parameter....)
}
};
And the search engine can be used as follows:
int main()
{
SearchEngine engine;
engine.addData(LuxuryRoom());
engine.addData(NormalRoom());
auto luxury_search_results = engine.search(LuxuryRoomCriteria()); //Search LuxuryRooms with the specific criteria and returns a LuxuryRoomSearchResults instance with the results of the search.
auto normal_search_results = engine.search(NormalRoomCriteria()); //Search NormalRooms with the specific criteria and returns a NormalRoomSearchResults instance with the results of the search.
}
The engine is based on store one vector for every system data type. And the engine uses a vector that stores that vectors.
We cannot have a polymorphic reference/pointer to vectors of different types, so we use a wrapper of a std::vector:
struct VectorWrapper
{
virtual ~VectorWrapper() = 0;
};
template<typename T>
struct GenericVectorWrapper : public VectorWrapper
{
std::vector<T> vector;
~GenericVectorWrapper() {};
};
//This template class "builds" the search engine set (vector) of system data types vectors:
template<int type_index>
struct VectorBuilder
{
static void append_data_type_vector(std::vector<VectorWrapper*>& data)
{
data.push_back( new GenericVectorWrapper< system_data_types::type_at<type_index> >() ); //Pushes back a vector that stores the indexth type of system data.
VectorBuilder<type_index+1>::append_data_type_vector(data); //Recursive call
}
};
//Base case (End of the list of system data types)
template<>
struct VectorBuilder<system_data_types::size>
{
static void append_data_type_vector(std::vector<VectorWrapper*>& data) {}
};
So the implementation of SearchEngine::_get_vector<T> is as follows:
template<typename T>
std::vector<T>& get_vector()
{
GenericVectorWrapper<T>* data; //Pointer to the corresponing vector
data = dynamic_cast<GenericVectorWrapper<T>*>(_data_lists[system_data_types::index_of<T>]); //We try a cast from pointer of wrapper-base-class to the expected type of vector wrapper
if( data )//If cast success, return a reference to the std::vector<T>
return data->vector;
else
throw; //Cast only fails if T is not a system data type. Note that if T is not a system data type, the cast result in a buffer overflow (index_of<T> returns -1)
}
The constructor of SearchEngine only uses VectorBuilder to build the list of vectors:
SearchEngine()
{
VectorBuilder<0>::append_data_type_vector(_data_list);
}
And the destructor only iterates over the list deleting the vectors:
~SearchEngine()
{
for(unsigned int i = 0 ; i < system_data_types::size ; ++i)
delete _data_list[i];
}
The advantages of this dessign are:
The search engine uses exactly the same interface for different searches (Searches with different system data types as target). And the process of "linking" a data type to its corresponding search criteria and results is done at compile-time.
That interface is type safe: A call to SearchEngine::search() returns a type of results based only on the search criteria passed. Assignament results errors are detected at compile-time. For example: NormalRoomResults = engine.search(LuxuryRoomCriteria()) generates a compilation error (engine.search<LuxuryRoomCriteria> returns LuxuryRoomResults).
The search engine is fully scalable: To add a new datatype to the system, you must only go to add the types to the typelists. The implementation of the search engine not changes.

Room Class
class Room{
public:
enum Type {
Regular,
Luxury,
Celebrity
};
Room(Type rt):roomType(rt), isOpen(true) { }
Type getRoomType() { return roomType; }
bool getRoomStatus() { return isOpen; }
void setRoomStatus(bool isOpen) { this->isOpen = isOpen; }
private:
Type roomType;
bool isOpen;
};
Hotel Class
class Hotel{
std::map<Room::Type, std::vector<Room*>> openRooms;
//std::map<Room::Type, std::vector<Room*>> reservedRooms;
public:
void addRooms(Room &room) { openRooms[room.getRoomType()].push_back(&room); }
auto getOpenRooms() {
std::vector<Room*> allOpenRooms;
for(auto rt : openRooms)
for(auto r : rt.second)
allOpenRooms.push_back(r);
return allOpenRooms;
}
auto getOpenRoomsOfType(Room::Type rt) {
std::vector<Room*> OpenRooms;
for(auto r : openRooms[rt])
OpenRooms.push_back(r);
return OpenRooms;
}
int totalOpenRooms() {
int roomCount=0;
for(auto rt : openRooms)
roomCount += rt.second.size();
return roomCount;
}
};
Client UseCase:
Hotel Marigold;
Room RegularRoom1(Room::Regular);
Room RegularRoom2(Room::Regular);
Room LuxuryRoom(Room::Luxury);
Marigold.addRooms(RegularRoom1);
Marigold.addRooms(RegularRoom2);
Marigold.addRooms(LuxuryRoom);
auto allRooms = Marigold.getOpenRooms();
auto LRooms = Marigold.getOpenRoomsOfType(Room::Luxury);
auto RRooms = Marigold.getOpenRoomsOfType(Room::Regular);
auto CRooms = Marigold.getOpenRoomsOfType(Room::Celebrity);
cout << " TotalOpenRooms : " << allRooms.size()
<< "\n Luxury : " << LRooms.size()
<< "\n Regular : " << RRooms.size()
<< "\n Celebrity : " << CRooms.size()
<< endl;
TotalOpenRooms : 4
Luxury : 2
Regular : 2
Celebrity : 0

If you really want to check that a room is of the same type as some other room, then typeid() is as good as any other method - and it's certainly "better" (from a performance perspective, at least) to calling a virtual method.
The other option is to not have separate classes at all, and store the roomtype as a member variable (and that is certainly how I would design it, but that's not a very good design for learning object orientation and inheritance - you don't get to inherit when the base class fulfils all your needs).

Related

Composite pattern with variations in classes

I have a situation where class A, B, and C, all are derived from X to make a composite pattern.
Now A, B, and C are very similar but they do have different internal attributes. My question is how do I access their attributes and modify them once they are added to composite without typecasting it?
In my real example, the part-whole relationship perfectly makes sense and all elements support the main operation which is why I need it. That said individual classes in the composite does have different attributes and requires special operations.
Is there a way to decouple these special properties and functions related to them away from composite class but attach to them?
To put things in perspective, let's say we have composite merchandise (base class of composite) which has a price. Now a store is a merchandise, in it, a department is a merchandise, in which an actual item say a potis merchandise, it can have a pot-set with combination pots which is also merchandise and so on.
class Merchandise
{
public:
virtual void add(Merchandise* item) = 0;
virtual Merchandise* getMerchandise() = 0;
virtual void show() = 0;
// assume we have the following key operations here but I am not implementing them to keep this eitemample short
//virtual setPrice(float price) = 0;
//virtual float getPrice() = 0;
};
class Store : public Merchandise
{
vector< Merchandise*> deparments;
std::string storeName = "";
public:
Store(std::string store_name) : storeName(store_name) {}
virtual void add(Merchandise* item)
{
deparments.push_back(item);
}
virtual Merchandise* getMerchandise()
{
if (deparments.size() > 0)
return deparments[0];
return 0;
}
virtual void show()
{
cout << "I am Store " << storeName << " and have following " << deparments.size() << " departments" << endl;
for (unsigned int i = 0; i < deparments.size(); i++)
{
deparments[i]->show();
}
}
};
class Department : public Merchandise
{
std::string depName;
vector<Merchandise*> items;
public:
Department(std::string dep_name) : depName(dep_name) {}
virtual void add(Merchandise* item)
{
items.push_back(item);
}
virtual Merchandise* getMerchandise()
{
if (items.size() > 0)
return items[0];
return 0;
}
virtual void show()
{
cout << "I am department " << depName << " and have following " << items.size() << " items" << endl;
for (unsigned int i = 0; i < items.size(); i++)
{
items[i]->show();
}
}
};
class Shirt : public Merchandise
{
std::string shirtName;
public:
Shirt(std::string shirt_name) : shirtName(shirt_name) {}
virtual void add(Merchandise* item) {}
virtual Merchandise* getMerchandise() { return 0; }
virtual void show()
{
cout << "I am shirt " << shirtName << endl;
};
};
class Pot : public Merchandise
{
std::string potName;
public:
Pot(std::string pot_name) : potName(pot_name) {}
virtual void add(Merchandise* item) { }
virtual Merchandise* getMerchandise() { return 0; }
virtual void show()
{
cout << "I am pot " << potName << endl;
};
int num = 0;
};
class CookSet : public Merchandise
{
std::string cooksetName;
vector<Merchandise*> pots;
public:
CookSet(std::string cookset_name) : cooksetName(cookset_name) {}
vector<Merchandise*> listOfPots;
virtual void add(Merchandise* item) { pots.push_back(item); }
virtual Merchandise* getMerchandise() { return 0; }
virtual void show()
{
cout << "I am cookset " << cooksetName << " and have following " << pots.size() << " items" << endl;
for (unsigned int i = 0; i < pots.size(); i++)
{
pots[i]->show();
}
};
int num = 0;
};
int main()
{
// create a store
Store * store = new Store( "BigMart");
// create home department and its items
Department * mens = new Department( "Mens");
mens->add(new Shirt("Columbia") );
mens->add(new Shirt("Wrangler") );
// likewise a new composite can be dress class which is made of a shirt and pants.
Department * kitchen = new Department("Kitchen");
// create kitchen department and its items
kitchen->add(new Pot("Avalon"));
CookSet * cookset = new CookSet("Faberware");
cookset->add(new Pot("Small Pot"));
cookset->add(new Pot("Big pot"));
kitchen->add(cookset);
store->add( mens );
store->add(kitchen);
store->show();
// so far so good but the real fun begins after this.
// Firt question is, how do we even access the deep down composite objects in the tree?
// this wil not really make sense!
Merchandise* item = store->getMerchandise()->getMerchandise();
// Which leads me to want to add specific method to CStore object like the following to retrieve each department
// but then does this break composite pattern? If not, how can I accomodate these methods only to CStore class?
//store->getMensDept();
//store->getsKitchenDept();
// Likewise a shirt class will store different attributes of a shirt like collar size, arm length etc, color.
// how to retrieve that?
// Other operations is, say if item is cookset, set it on 20% sale.
// Another if its a shirt and color is orange, set it on 25% sale (a shirt has a color property but pot doesn't).
// how to even dispaly particular attributes of that item in a structure?
// item->getAttributes();
return 0;
}
The problem is with this line once I have filled up the composite.
Merchandise* item = store->getMerchandise()->getMerchandise();
First, from my code structure, I know this should be a certain type but as a best practice, we are not supposed to typecast this!? But I do want to change its properties which are unique to it so how do I achieve that?
Assume this store sells shirts as well and I want to change its properties (or even just to display them!), it is very different from a pot.
What would be the best approach here? I think if we can somehow decouple the unique properties of each composite into different classes, this way composite will stay leaner too but not sure how to achieve that.
I assume, in real life, there is no perfect composite and the constituent classes will have some differences. How do we handle that?
Update
Please note I have used the merchandise example to explain the problem. In my real example, A, B, C are all derived from X. A contains multiple B which contains multiple C items. When an operation is performed on A, it needs to be performed on its constituents and that's why I am using composite. But then, each composite does have different attributes. Is composite not a good fit for this?
I think you are looking for the visitor design pattern, it keeps clean interfaces and makes code much more flexible.
class Shirt;
class Pot;
class visitor{
public:
//To do for each component types:
virtual void visit(Shirt&) =0;
virtual void visit(Pot&) =0;
};
class Merchandise
{
public:
//...
//Provides a default implementation for the acceptor that
//that do nothing.
virtual void accept(visitor& x){}
// assume we have the following key operations here but I am not implementing them to keep this eitemample short
//virtual setPrice(float price) = 0;
//virtual float getPrice() = 0;
// => implementable with a visitor
};
class Store : public Merchandise
{
//...
void accept(visitor& v) override
{
for (unsigned int i = 0; i < deparments.size(); i++)
{
//forward the visitor to each component of the store.
//do the same for departments
deparments[i]->accept(v);
}
}
};
class Shirt : public Merchandise
{
//...
void accept(visitor& v) override{
//now *this as the type Shirt&, pass this to the visitor.
v.visit(*this);
}
};
class Pot : public Merchandise
{
//...
void accept(visitor& v) override{
v.visit(*this);
}
};
class SpecialPromotion
:public visitor{
public:
void visit(Shirt& s) override {
//25% discount on orange shirts, 15% otherwise;
if (s.color="orange")
s.price*=0.75;
else
s.price*=0.85
}
void visit(Pot& p) override{
//one pot offered for each pot pack
++p.num;
}
};
//example of usage:
void apply_special_promotion(Store& s){
SpecialPromotion x;
s.accept(x);
}
class EstimateStockMass
:public visitor{
public:
double mass=0;
void visit(Shirt& s) override {
if (s.size="XL") mass+=0.5;
else mass+=0.1;
}
void visit(Pot& p) override{
mass += p.num * 1.2;
}
};
//example of usage:
double get_stock_mass(Store& s){
EstimateStockMass x;
s.accept(x);
return x.mass;
}
It seems like what you want to do is gathering RTTI (Run-time type information), so dynamic_cast is the solution. Also, if you are using C++17, I will recommend you use std::variant (or boost::variant if you are using lower version of C++ but are using boost.) If you do not want to use them, then maybe you can make your add a template function and returns a reference to the underlying type.
By the way
in your main there are a bunch of dynamic allocations that never get deallocated. Use smart pointers if you have C++ with version at least C++11.
your base classes do not have virtual destructors, this will cause huge problems when destroying your store
Don't use using namespace std
If you have C++11, use override keyword when you want to override a virtual function
You should mark show() const.
Merchandise: a commodity offered for sale
Now a store is a merchandise,
This is true only if you are selling the store. Otherwise it is better described as a container of merchandise, not a composite.
Even if you are in the business of selling stores, it might (depending on the context of the program) be prudent to treat it as a different kind of merchandise (different base class) since the selling environment is rather different.
in it, a department is a merchandise,
Departments within a store are rarely sold to other stores, so I highly doubt this claim. Again, you have something that contains merchandise, not something composed of merchandise.
in which an actual item say a pot is merchandise, it can have a pot-set with combination pots which is also merchandise and so on.
Yes, this much is good. Pots are offered for sale. A pot-set combination sounds like a good example of composite merchandise, as the set is offered for sale, and its components might be packaged and offered for sale separately, perhaps even on the same shelf.
Speaking of the shelf, that would be another example of a container of merchandise.
It looks like you might be after containers rather than composites. A store could have a vector of departments (or perhaps a set if you want to find them by name). The department in turn could have a container of merchandise within that department. Or perhaps the department would contain aisles, which then contain shelves, which then contain merchandise.
If you need the inventory of the entire store, there are a few options. One would be to have the store iterate over its departments, which then iterate over their inventories. Ideally, you would implement this within the store class so that code outside the class does not need to know about this structure. Another option would be for the store to maintain its own container of merchandise. This would mean that a single item of merchandise would logically be in multiple containers (e.g. a store's and a department's). This suggests using containers of shared_ptr, so that each item of merchandise is still represented by a single data object.
Regardless of the implementation chosen, the relationship between "store" and "merchandise" is better described as "has" rather than "is".
Concerning Your Design Choices
Let's compare the GoF book description of the intent of the Composite pattern with your requirements:
Compose objects into tree structures that represent whole-part hierarchies.
In your example a shirt is not part of a shop, and a shop is not really an item of merchandise.
You say this does make sense in your actual code, but I can only comment on the code you actually showed.
Composite lets clients treat individual objects and compositions of objects uniformly.
In your comment you say you don't really want to treat each type of object uniformly.
So, it's at least not obvious that Composite is a good fit, because the description doesn't match your use case (at best, it half-fits your described use case but not your sample code).
For comparison, the motivating example from that book is a drawing app, where treating the main canvas as a Drawable object containing other Drawable sub-objects (of different types like lines, polygons and text) is useful for rendering. In that case each object is a drawable part of the drawable whole, and it focuses on the case when you do want to treat them uniformly (ie, issuing a single draw call to the top-level canvas).
A match has an innings (has score/result), both side which are playing has their inning (has scores), then each player who is playing has his innings (score with more details). When match progresses, an event is added to the match which than is added to current innings and than current player innings. So I build up score this way but at the end I want to display it and each innings type is rather different and setting up current state requires different operations.
OK, an innings is part of a match, but do you have any more levels? Does an innings consist of balls or other events, or is it just ... an innings number, a player and a score?
If you can easily model a game as a list of innings, and link each innings to a player for per-player reports, that seems a lot simpler.
Even if you have another level, if you only want to deal with objects heterogeneously (according to their different types, instead of homogeneously as if they're all the same), you can just write that structure explicitly.
Concerning Your Composite Implementation
The getMerchandise interface is poor. Why does it return only the first of potentially many objects? Why do you need to get them anyway?
You mention two use cases:
changing an object's properties
Presumably you know which object you want to alter, right? Say you want to change the price of the object loosely identified as Mens>Shirts>Wrangler. Either
ask the store to forward a Visitor to that object by name (so the store finds the department called "Mens" and asks that to forward the Visitor to a child matching Shirts>Wrangler).
just find the Shirt("Wrangler") object directly in some other index (eg. by stock number) and deal with it directly. You don't have to do everything via the Composite pattern even if you do use it.
displaying an object
But the whole point of the Composite pattern is that every object should implement a virtual display (or whatever) method. You use this when you want to let every type know how to display itself.

What is the "correct OOP" way to deal with a storage pool of items of mixed types?

This was inspired by a comment to my other question here:
How do you "not repeat yourself" when giving a class an accessible "name" in C++?
nvoight: "RTTI is bad because it's a hint you are not doing good OOP. Doing your own homebrew RTTI does not make it better OOP, it just means you are reinventing the wheel on top of bad OOP."
So what is the "good OOP" solution here? The problem is this. The program is in C++, so there are also C++ specific details mentioned below. I have a "component" class (actually, a struct), which is subclassed into a number of different derived classes containing different kinds of component data. It's part of an "entity component system" design for a game. I'm wondering about the storage of the components. In particular, the current storage system has:
a "component manager" which stores an array, actually a hash map, of a single type of component. The hash map allows for lookup of a component by the entity ID of the entity it belongs to. This component manager is a template which inherits from a base, and the template parameter is the type of component to manage.
a full storage pack which is a collection of these component managers, implemented as an array of pointers to the component manager base class. This has methods to insert and extract an entity (on insertion, the components are taken out and put into the managers, on removal, they are extracted and collected into a new entity object), as well as ones to add new component managers, so if we want to add a new component type to the game, all we have to do is put another command to insert a component manager for it.
It's the full storage pack that prompted this. In particular, it offers no way of accessing a particular type of component. All the components are stored as base class pointers with no type information. What I thought of was using some kind of RTTI and storing the component managers in a map which maps type names and thus allows for lookup and then the proper downcasting of the base class pointer to the appropriate derived class (the user would call a template member on the entity storage pool to do this).
But if this RTTI means bad OOP, what would be the correct way to design this system so no RTTI is required?
Disclaimer/resources: my BCS thesis was about the design and implementation of a C++14 library for compile-time Entity-Component-System pattern generation. You can find the library here on GitHub.
This answer is meant to give you a broad overview of some techniques/ideas you can apply to implement the Entity-Component-System pattern depending on whether or not component/system types are known at compile-time.
If you want to see implementation details, I suggest you to check out my library (linked above) for an entirely compile-time based approach. diana is a very nice C library that can give you an idea of a run-time based approach.
You have several approaches, depending on the scope/scale of your project and on the nature of your entities/components/systems.
All component types and system types are known at compile-time.
This is the case analyzed in my BCS thesis - what you can do is use advanced metaprogramming techniques (e.g. using Boost.Hana) to put all component types and system types in compile-time lists and create data structures that link everything together at compile time. Pseudocode example:
namespace c
{
struct position { vec2f _v };
struct velocity { vec2f _v };
struct acceleration { vec2f _v };
struct render { sprite _s; };
}
constexpr auto component_types = type_list
{
component_type<c::position>,
component_type<c::velocity>,
component_type<c::acceleration>,
component_type<c::render>
};
After defining your components, you can define your systems and tell them "what components to use":
namespace s
{
struct movement
{
template <typename TData>
void process(TData& data, float ft)
{
data.for_entities([&](auto eid)
{
auto& p = data.get(eid, component_type<c::position>)._v;
auto& v = data.get(eid, component_type<c::velocity>)._v;
auto& a = data.get(eid, component_type<c::acceleration>)._v;
v += a * ft;
p += v * ft;
});
}
};
struct render
{
template <typename TData>
void process(TData& data)
{
data.for_entities([&](auto eid)
{
auto& p = data.get(eid, component_type<c::position>)._v;
auto& s = data.get(eid, component_type<c::render>)._s;
s.set_position(p);
some_context::draw(s);
});
}
};
}
constexpr auto system_types = type_list
{
system_type<s::movement,
uses
(
component_type<c::position>,
component_type<c::velocity>,
component_type<c::acceleration>
)>,
system_type<s::render,
uses
(
component_type<c::render>
)>
};
All that's left is using some sort of context object and lambda overloading to visit the systems and call their processing methods:
ctx.visit_systems(
[ft](auto& data, s::movement& s)
{
s.process(data, ft);
},
[](auto& data, s::render& s)
{
s.process(data);
});
You can use all the compile-time knowledge to generate appropriate data structures for components and systems inside the context object.
This is the approach I used in my thesis and library - I talked about it at C++Now 2016: "Implementation of a multithreaded compile-time ECS in C++14".
All component types and systems types are known at run-time.
This is a completely different situation - you need to use some sort of type-erasure technique to dynamically deal with components and systems. A suitable solution is using a scripting language such as LUA to deal with system logic and/or component structure (a more efficient simple component definition language can also be handwritten, so that it maps one-to-one to C++ types or to your engine's types).
You need some sort of context object where you can register component types and system types at run-time. I suggest either using unique incrementing IDs or some sort of UUIDs to identify component/system types. After mapping system logic and component structures to IDs, you can pass those around in your ECS implementation to retrieve data and process entities. You can store component data in generic resizable buffers (or associative maps, for big containers) that can be modified at run-time thanks to component structure knowledge - here's an example of what I mean:
auto c_position_id = ctx.register_component_type("./c_position.txt");
// ...
auto context::register_component_type(const std::string& path)
{
auto& storage = this->component_storage.create_buffer();
auto file_contents = get_contents_from_path(path);
for_parsed_lines_in(file_contents, [&](auto line)
{
if(line.type == "int")
{
storage.append_data_definition(sizeof(int));
}
else if(line.type == "float")
{
storage.append_data_definition(sizeof(float));
}
});
return next_unique_component_type_id++;
}
Some component types and system types are known at compile-time, others are known at run-time.
Use approach (1), and create some sort of "bridge" component and system types that implements any type-erasure technique in order to access component structure or system logic at run-time. An std::map<runtime_system_id, std::function<...>> can work for run-time system logic processing. An std::unique_ptr<runtime_component_data> or an std::aligned_storage_t<some_reasonable_size> can work for run-time component structure.
To answer your question:
But if this RTTI means bad OOP, what would be the correct way to design this system so no RTTI is required?
You need a way of mapping types to values that you can use at run-time: RTTI is an appropriate way of doing that.
If you do not want to use RTTI and you still want to use polymorphic inheritance to define your component types, you need to implement a way to retrieve some sort of run-time type ID from a derived component type. Here's a primitive way of doing that:
namespace impl
{
auto get_next_type_id()
{
static std::size_t next_type_id{0};
return next_type_id++;
}
template <typename T>
struct type_id_storage
{
static const std::size_t id;
};
template <typename T>
const std::size_t type_id_storage<T>::id{get_next_type_id()};
}
template <typename T>
auto get_type_id()
{
return impl::type_id_storage<T>::id;
}
Explanation: get_next_type_id is a non-static function (shared between translation units) that stores a static incremental counter of type IDs. To retrieve the unique type ID that matches a specific component type you can call:
auto position_id = get_type_id<position_component>();
The get_type_id "public" function will retrieve the unique ID from the corresponding instantiation of impl::type_id_storage, that calls get_next_type_id() on construction, which in turn returns its current next_type_id counter value and increments it for the next type.
Particular care for this kind of approach needs to be taken to make sure it behaves correctly over multiple translation units and to avoid race conditions (in case your ECS is multithreaded). (More info here.)
Now, to solve your issue:
It's the full storage pack that prompted this. In particular, it offers no way of accessing a particular type of component.
// Executes `f` on every component of type `T`.
template <typename T, typename TF>
void storage_pack::for_components(TF&& f)
{
auto& data = this->_component_map[get_type_id<T>()];
for(component_base* cb : data)
{
f(static_cast<T&>(*cb));
}
}
You can see this pattern in use in my old and abandoned SSVEntitySystem library. You can see an RTTI-based approach in my old and outdated “Implementation of a component-based entity system in modern C++” CppCon 2015 talk.
Despite the good and long answer by #VittorioRomeo, I'd like to show another possible approach to the problem.
Basic concepts involved here are type erasure and double dispatching.
The one below is a minimal, working example:
#include <map>
#include <vector>
#include <cstddef>
#include <iostream>
#include <memory>
struct base_component {
static std::size_t next() noexcept {
static std::size_t v = 0;
return v++;
}
};
template<typename D>
struct component: base_component {
static std::size_t type() noexcept {
static const std::size_t t = base_component::next();
return t;
}
};
struct component_x: component<component_x> { };
struct component_y: component<component_y> { };
struct systems {
void elaborate(std::size_t id, component_x &) { std::cout << id << ": x" << std::endl; }
void elaborate(std::size_t id, component_y &) { std::cout << id << ": y" << std::endl; }
};
template<typename C>
struct component_manager {
std::map<std::size_t, C> id_component;
};
struct pack {
struct base_handler {
virtual void accept(systems *) = 0;
};
template<typename C>
struct handler: base_handler {
void accept(systems *s) {
for(auto &&el: manager.id_component) s->elaborate(el.first, el.second);
}
component_manager<C> manager;
};
template<typename C>
void add(std::size_t id) {
if(handlers.find(C::type()) == handlers.cend()) {
handlers[C::type()] = std::make_unique<handler<C>>();
}
handler<C> &h = static_cast<handler<C>&>(*handlers[C::type()].get());
h.manager.id_component[id] = C{};
}
template<typename C>
void walk(systems *s) {
if(handlers.find(C::type()) != handlers.cend()) {
handlers[C::type()]->accept(s);
}
}
private:
std::map<std::size_t, std::unique_ptr<base_handler>> handlers;
};
int main() {
pack coll;
coll.add<component_x>(1);
coll.add<component_y>(1);
coll.add<component_x>(2);
systems sys;
coll.walk<component_x>(&sys);
coll.walk<component_y>(&sys);
}
I tried to be true to the few points mentioned by the OP, so as to provide a solution that fits the real problem.
Let me know with a comment if the example is clear enough for itself or if a few more details are required to fully explain how and why it works actually.
If I understand correctly, you want a collection, such as a map, where the values are of different type, and you want to know what type is each value (so you can downcast it).
Now, a "good OOP" is a design which you don't need to downcast. You just call the mothods (which are common to the base class and the deriveratives) and the derived class performs a different operation than its parent for the same method.
If this is not the case, for example, where you need to use some other data from the child and thus you want to downcast, it means, in most cases, you didn't work hard enough on the design. I don't say it's always possible, but you need to design it in such a way the polymorphism is your only tool. That's a "good OOP".
Anyway, if you really need to downcast, you don't have to use RTTI. You can use a common field (string) in the base class, that marks the class type.

Implementing a map with unknown key type

I'm a physicist working on a code that needs to construct and then cache a number of matrices of different type. The matrices then need to be retrievable, given a set of information that uniquely specifies them (a 'key'). I'm trying to find a flexible way of doing this.
My current approach involves bundling together all the 'key' information into a single class:
class FilterKey{
public:
FilterKey(const char type, const X key1, const Y* key2...);
~FilterKey();
bool operator==(const FilterKey& rhs) const;
private:
char type; X mKey1; Y* mpKey2;
}
User code passes these to an interface class called "MatrixDirectory", instantiated as a global variable (but not a singleton) that stores a map between types and caches:
//MatrixDirectory.hpp
class MatrixDirectory : private NonCopyable{
public:
void Clear(); //Calls destructors.
void Filter(std::vector<double>& U, const FilterKey& key);
private:
std::map<char types, FilterCache* caches> mpFilterCaches;
};
When MatrixDirectory::Filter encounters a new char type it calls the FilterCacheFactory, which constructs the appropriate derived class from FilterCache based on the char:
//FilterCacheFactory.cpp
namespace FilterCacheFactory{
FilterCache* MakeFilterCache(char type) {
if '1'==type return new OneDFilterCache();
else if 'S'==type return new S2FilterCache();
else if 'B'==type return new B2FilterCache();
else REQUIRE(False, "Invalid filter type '" << type << "'!");
}
}
and inserts the result into the map. The actual FilterCaches then manage the matrices and perform whatever filtering is necessary, given a FilterKey.
This, or approximately this, I think will work. But I'm a bit concerned about having to be so explicit about FilterKey. It would be better if MatrixDirectory could store also FilterCaches that are keyed on different types. I guess I could do this with variadic templates but I don't want to have to directly #include everything. Is there another way?

Associate properties with class instances at runtime

Is there an idiomatic C++ way to dynamically associate properties with a fixed set of class instances?
Suppose for instance we have an Element class. Every element always has certain properties that are contained by member variables.
struct Element {
unsigned atomic_protons;
float mass;
};
There are other properties we might associate with each Element but not every program using the Element class will be interested in the same properties. Maybe sometimes we're interested in taste and sometimes we're interested in color and variables representing these properties might be expensive to initialize. Maybe we don't even know until runtime what properties we will want.
The solution that comes to my mind is a set of parallel arrays. One array contains the instances themselves and the indices of that array implicitly associate each instance with items in a series of ``parallel'' arrays.
// fixed set of Element instances
std::vector<Element> elements;
// dynamic properties
std::vector<Flavor> element_flavors;
std::vector<Color> element_colors;
Each vector of properties is created as necessary.
This solution is ok but does not at all resemble idiomatic C++. Besides aesthetics, this arrangement makes it awkward to look up a property from a given Element instance. We would need to insert an array index into each Element instance. Also, the size information in each vector is redundant.
It has the plus that if we're interested in all the values of a given property the data is arranged appropriately. Usually however, we want to go in the opposite direction.
Solutions that modify the Element class in some way are fine so long as the class need not be modified every time a new property is added. Assume also that there exist methods for working with the Element class that all programs share and we don't want those methods to break.
I think the std::unordered_map<Element*, Flavor> solution that PiotrNycz suggested is a perfectly "idomatic" way of associating a Flavor with a particular Element but I wanted to suggest an alternative.
Providing the operations you would like to perform on an Element are fixed you can extract out an interface:
class IElement {
public:
virtual ~IElement() {}
virtual void someOperation() = 0;
};
Then you can easily store a collection of IElement pointers (preferably smart pointers) and then specialize as you wish. With different specializations having different behavior and containing different properties. You could have a factory that decided which specialization to create at runtime:
std::unique_ptr<IElement>
elementFactory(unsigned protons, float mass, std::string flavor) {
if (!flavor.isEmpty()) // Create specialized Flavored Element
return std::make_unique<FlavoredElement>(protons, mass, std::move(flavor));
// Create other specializations...
return std::make_unique<Element>(protons, mass); // Create normal element
}
The problem in your case is you could easily get an explosion of specializations: Element, FlavoredElement, ColoredElement, FlavoredColoredElement, TexturedFlavoredElement, etc...
One pattern that is applicable in this case is the Decorator pattern. You make FlavoredElement a decorator that wraps an IElement but also implements the IElement interface. Then you can choose to add a flavor to an element at runtime:
class Element : public IElement {
private:
unsigned atomic_protons_;
float mass_;
public:
Element(unsigned protons, float mass) : atomic_protons_(protons), mass_(mass) {}
void someOperation() override { /* do normal thing Elements do... */ }
};
class FlavoredElement : public IElement {
private:
std::unique_ptr<IElement> element_;
std::string flavor_;
public:
FlavoredElement(std::unique_ptr<IElement> &&element, std::string flavor) :
element_(std::move(element)), flavor_(std::move(flavor)) {}
void someOperation() override {
// do special thing Flavored Elements do...
element_->someOperation();
}
};
class ColoredElement : public IElement {
private:
std::unique_ptr<IElement> element_;
std::string color_;
public:
ColoredElement(std::unique_ptr<IElement> &&element, std::string color) :
element_(std::move(element)), color_(std::move(color)) {}
void someOperation() override {
// do special thing Colored Elements do...
element_->someOperation();
}
};
int main() {
auto carbon = std::make_unique<Element>(6u, 12.0f);
auto polonium = std::make_unique<Element>(84u, 209.0f);
auto strawberry_polonium = std::make_unique<FlavoredElement>(std::move(polonium), "strawberry");
auto pink_strawberry_polonium = std::make_unique<ColoredElement>(std::move(strawberry_polonium), "pink");
std::vector<std::unique_ptr<IElement>> elements;
elements.push_back(std::move(carbon));
elements.push_back(std::move(pink_strawberry_polonium));
for (auto& element : elements)
element->someOperation();
}
So, there are two cases.
You can attach property to a program in a static way. But this property must be known before compilation. And yes, there is idiomatic way to do so. It is called specialization, derivation or inheritance:
struct ProgramASpecificElement : Element
{
int someNewProperty;
};
Second case is more interesting. When you want to add property at runtime. Then you can use map, like this:
std::unordered_map<Element*, int> elementNewProperties;
Element a;
elementNewProperties[&a] = 7;
cout << "New property of a is: " << elementNewProperties[&a];
IF you do not want to pay performance penalty for searching in a map, then you can predict in an Element that it might have new properties:
struct Property {
virtual ~Property() {}
};
template <typename T>
struct SimpleProperty : Property {
T value;
};
struct Elememt {
// fixed properties, i.e. member variables
// ,,,
std::unordered_map<std::string, Property*> runtimeProperties;
};
Element a;
a.runtimeProperties["age"] = new SimpleProperty<int>{ 7 };
cout << "Age: " << *dynamic_cast<SimpleProperty<int>*>(a.runtimeProperties["age"]);
OF course the code above is without any necessary validations and encapsulations - just a few examples.

How to use a std::string with inheritance as parameter?

I'm currently working on a college project with C++ and one of my assignments is to make a social network using inheritance and polymorphism. Currently I have a Node class that is used on a Map and Multimap (both are created manually and not used from the std). The node can hold two variables (key and data for example) and where I'm using it, the first variable can either be a pointer or a string (they let us use std::string).
The problem I'm having is that when I inherit from the "root" class (Object) and use "Object" as a data type for "key", I'm unable to pass a string created with the std as parameter to its constructor, because it doesn't inherit from my Object class. One solution is to implement my own string class and make it inherit from Object, but I was searching for other workarounds.
If there's any problem with the logic above, please tell me as I'm just beginning with C++.
EDIT 1 (some code for my Node):
class TempNode
{
private:
TempNode* next;
Key key;
T value;
public:
TempNode();
explicit TempNode(const Key thisKey, const T thisValue, TempNode* thisNext = NULL)
: key(thisKey)
, value(thisValue)
, next(thisNext)
{
}
inline Key getKey() { return key; }
inline T getValue() { return value; }
inline TempNode* getNext() { return next; }
inline void setNext(TempNode* thisNext) { next = thisNext; }
};
The string or Person types are currently used only in key, but that is with another implementation using templates (which works fine), but my teacher now requires us to apply inheritance to the entire project (to get used to it I guess).
To implement this using inheritance, you think of Key as a data type specifically designed as a key in your map/multimap implementation. Key inherits from Object, but it may provide its own, key-specific functions, such as – for example – a function repr() which generates a representation used by the map for some map-specific operations (maybe as a basis for hashing, or sorting or whatever).
The map/multimap must be used in such a way that the Key objects are stored as pointers (or std::unique_ptr, or std::shared_ptr, or whatever is appropriate), but not as copies of Key objects.
So we have:
struct Object
{
virtual ~Object()
{ }
};
/* Key class. Pointers of this type are inserted
into the map. */
class Key : public Object
{
public:
/* Must be supported by all keys: */
virtual std::string repr() const = 0;
};
We also assume there is a separate definition of Person objects:
struct Person : Object
{
Person(const std::string &name)
: name_(name)
{ }
std::string name_;
};
According to your specification, there are two flavours of Key: One that represents strings and must be initialized using a string, and another one that represents persons and must be initialized by a person pointer (I'll assume that the person-keys do not actually own these pointers, so you need to make sure the person objects they point to stay alive as long as the person-key exists).
We implement this by specializing Key into two derived classes, a PersonKey and a StringKey:
class PersonKey : public Key
{
public:
PersonKey(Person *person_ptr)
: Key() , person_ptr_(person_ptr)
{ }
virtual std::string repr() const
{
if (person_ptr_ != 0)
return std::string("Person/") + person_ptr_->name_;
else
return "<NUL>";
}
private:
Person *person_ptr_;
};
class StringKey : public Key
{
public:
StringKey(const std::string &str)
: Key() , str_(str)
{ }
virtual std::string repr() const
{
return str_;
}
private:
std::string str_;
};
When you make insertions into your map/multimap, you generate Key objects (which you represent as Key* or Key& or std::unique_ptr<Key>). When you want to insert a string, you generate them as StringKey objects, and when you want to insert them as person-pointers, you use PersonKey – but the data type of the key you insert will not reflect the specialization.
Here is an example of a general Key object (implemented as std::unique_ptr<Key>, but you may just use Key* if you are not afraid of memory leaks):
int main()
{
/* General key object: */
std::unique_ptr<Key> mykey;
/* Now it points to a string-key, initialized using
a string, as required: */
mykey.reset(new StringKey("hello"));
std::cout << "repr(mykey) == \""
<< mykey->repr()
<< '"'
<< std::endl;
/* Now the same key object is made to refer to
a person object: */
Person person("me");
mykey.reset(new PersonKey(&person));
std::cout << "repr(mykey) == \""
<< mykey->repr()
<< '"'
<< std::endl;
return 0;
}
Necessary headers for the code above are:
#include <iostream>
#include <memory>
#include <string>
(But memory is only required for my use of std::unique_ptr, which is not actually necessary to solve your problem.)
I think what you are really looking for are templates. Your solution with "root object" won't work as you can see with standard objects and external libraries but also you will not be able to use your containers with primitives (for example person id(as int) as key, and Person class as value).
With templates you can say what type you are going to work with at compile time and compiler will help you to obey your own rules. It can be declared like this:
template<class T1, class T2>
class Map{
T1 key;
T2 value;
(...)
}
Then you can use it more or less like this:
Map<std::String, int> phoneBook;
And compiler will guard you and warn, if you try to add, for example float instead of int, to you Map. But before you start coding I advice you to read some tutorials first, or maybe even some book on c++ in general. But if you want to start with generic right now, you can start her:
http://www.cplusplus.com/doc/tutorial/templates/
The only way you'd be able to store a string in your Object variable was if the string class inherited from your Object class, so you will have to implement your own String class unfortunately.
The real flaw here is that you are taking a Java/C# approach to design, where an Object variable can hold anything. In C++ the proper way to handle such things is through the use of templates, supposing your Map/Multimap/Node only need to hold one specific data type.
If your container needs to be able to hold any arbitrary data type, I would recommend using type erasure, although that can be a bit complicated for a beginner.