I need to implement a BST for 2 types of objects; accounts and customers. Both these types have a pk (both called "id").
When I google / reseach for a solution, I can find loads of BST solutions, but they all seem to work with simple types, e.g. the BST solution can sort integers like {1,2,3,4,99,...999} - but I want the BST solution to sort complex types / classes, based on their pk. Is this possible / any tips? Thanks.
The logic used to build a binary search tree of integers should immediately be generalizable to other data types. Instead of storing an integer as a key in each node, instead store your customer and account names in each field, but when doing lookups only compare the primary key. It really should be that simple.
That said, unless this is for a homework assignment, you should just use std::map, which has all the same complexity guarantees of a binary search tree but is already written for you, widely available, and heavily tested.
Hope this helps!
One way is to modify you node definition, adding a reference to the object you want to store and set an index (pk in your case), to sort the notes. On using different types, templates might solve:
class Account
{
public:
Account(int k) : pk(k) {};
int pk;
/* ... */
};
template<typename T>
class TreeNode
{
public:
TreeNode(T& the_object) : id(the_object.pk), object(the_object) {};
int id;
T& object;
};
int main () {
Account a(9);
TreeNode<Account> tn(a);
std::cout << tn.id;
}
If you want to mix different object types in a tree, the other way is to define a class containing an accessor to the pk attribute and make the account and customer inhere it, for example:
class Indexable
{
public:
Indexable(int the_pk) : pk(the_pk) {};
int pk;
};
class Account : public Indexable
{
public:
Account(int k) : Indexable(k) {};
/* ... */
};
class TreeNode
{
public:
TreeNode(Indexable& the_object) : id(the_object.pk), object(the_object) {};
int id;
Indexable& object;
};
int main () {
Account a(9);
TreeNode tn(a);
std::cout << tn.id;
}
As the other answers pointed out, in production you might consider using std::map or another known library of tree structures.
Related
\\while implementing a solution found with casting over a a common baseclass
\\ with virtal members.
\\i found out about univeral references, because that is a other question i created a new one:
https://stackoverflow.com/questions/29590429/c-is-it-possible-to-use-universal-references-for-a-tree-forest-with-nodes-that
please refer also to this original question.
I want to organize the hierarchical tree structure of objects with different types.
The solution should do its work at compile time, so i found out that i have to do this with templates at best without any casting.
after some try's that are in the editlog and had some fundamental flaws.
i found a way to split it up in two classes
forest that stores all the nodes and does give them two coordinates the indexnumbers in the vector< vector <...> >
node a template < T > that stores the T Object and the relationship to the other nodes
my idea does not yet have a way to access the memberfunctions of the Objects stored inside the node class, without breaking the unique_pointer that secures the resourcemanagement.
and i would like to know how to ensure typesafty when accessing a object inside the node class.
there may be errors inside the code, i'm quite sure that it will not compile, the question is about the concept.
Questions are inside the comments.
verion 4:
class Forest
{
public:
template<typename T>
{friend class Node<T>;} \\every Node<T> should have access to the forest
Forest();
~Forest();
Forest(const Forest&)=delete; \\does not make any sense to copy or assign the forest to another forest.
Forest operator=(const Forest&)=delete;
int insertroot() \\every tree has a void nullptr seed/root so that the forest does not need to be templatet, returns the treenumber
{ \\implementation
if(this->Nodes.size()==0)
{
std::vector<std::unique_ptr<Node<void> > > h0;
h0.push_back(std::unique_ptr<Node<void>(new Node<void>(nullptr));
this->Nodes.push_back(h0);
}else
{
this->Nodes[0].push_back(std::unique_ptr<Node<void>(new Node<void>(nullptr,this)));
}
return this->Nodes[0].size()-1;
}
Node<void>* getroot(int i) \\ to later allow access to the children and the Objects inside them
{
if(Nodes.size>0){
if((i>0)&(i<Nodes[0].size()))
{
return Nodes[0][i].get();
}
}
private:
std::vector<std::vector<unique_ptr<Node<void> > > nodes; \\is it possible to fill this vector with any Node<T>? its a vector*2 to a unique_ptr to a classpointer with a member pointer to any class. from what i read about templates they create a extra class for every type, so basicly the unique_ptr have a different type and i cannot store different types in a vector without casting?
}
template<typename T>
class Node
{
public:
Node(T n,Forest * Fo) \\ every Node is in the forest and has access to the other nodes and forest information
:object(std::unique_ptr(n)),F(Fo)
{
if(n==nullptr)
{
this->lvl=0;
this->place=F->Node[0].size();
this->parent=-1;
}
}
~Node();
Node(const Node&)=delete;
Node operator=(const Node&)=delete;
T getObject(){return object.get();} \\how does the compiler know the type? see getchild
template<typename C>
{
Node<C> * getchild(int){} \\not yet exsisting implementation of get child[int] how do i teach the compiler what int responds to what type?
\\when i understand templates correct then Node<C> are different Classes for every C??
addChild(C c)
{
Node * n=new Node(c,this->F);
n->parent=this->place;
n->lvl=this->lvl+1
if(F->nodes.size()<=n->lvl)
{
n->place=0;
h0=std::vector<unique_ptr<Node<C>> >;
h0.push_back(unique_ptr<Node<C>(n))
F->Nodes.push_back(h0); \\are vector<uniptrNode<C> > and vector<uniptrNode<void>> compatible?
}else
{
n->place=F->nodes[n->lvl].size();
F->Nodes[n->lvl].push_back(unique_ptr<Node<C> >(n));
}
this->children.push_back(c->place);
}
}
private:
int parent,place,lvl;
std::vector<int> children;
unique_ptr<T> object;
Forest * F;
}
does anyone know a way to implement a container like this?
maybe there is some abstract type type i did not find out about, so do that i can add a method type getnodetype(int) or checknodetype(int,type), could i assign this with auto nodex=y->getObject()? But then how does the compiler know what methodes nodex has and does not has?
EDIT: i removed the original Post because v4 is very close to a working solution version 1-3 should be in the editlog
I think you need something like boost.any or QVariant.
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.
I just started learning C++ and I'm used to Java paradigms, so I'm not sure how this should be done:
I need to represent a vector of products of two different types: packaged and fresh food. They have some common fields with a single implementation (availability, re-stock quantity etc), but they have also different fields and functions with different return types.
I.E: fresh foods may have a boolean field needsRefrigeration, other
products may have an integer representing a category (food, cleaning,
bricolage, forniture...).
In Java I would create a Product object with the common fields and a PackagedProduct (extending Product) plus a FreshProduct (also extending Product) with their particular fields. Then I'd place every product in a vector and accessed as Product when I need the common fields, safely-casted (with instanceof) to the right class when I need to access the child's fields.
I know this is not the right way in C++ and I don't want to force java programming paradigms to C++.
I can imagine:
create all the functions required by all the cildren as virtual in the parent and add a field in the parent representing the type of the cild, so it can safely casted
create a wrapper object containing two different vectors, each one of the type of a child object and return the values in the correct order, eventually using a third int vector.
I think these solutions are really bad, and I'm almost sure there must be a better way, but I can't imagine it. Can you help me?
What's the right way to do this?
I need to represent a vector of products of two different types: packaged and fresh food.
Do you really need both types of product in the same vector? Can't you have two vectors?
std::vector<PackagedProduct> packaged;
std::vector<FreshProduct> fresh;
packaged.emplace_back(1, 2, 3);
fresh.emplace_back(4, 5, 6);
This will be by far the most efficient solution. (Less indirections keep the prefetcher happy.)
If you absolutely need both kinds of products in the same vector, you must use indirection:
std::vector<std::unique_ptr<Product>> products;
products.push_back(std::make_unique<PackagedProduct>(1, 2, 3));
products.push_back(std::make_unique<FreshProduct>(4, 5, 6));
Instead of checking the dynamic type at runtime and downcast, you should read up on virtual methods.
The basic idea is the same as in Java: Use inheritance to create a class hierarchy:
class Product { public: virtual ~Product(); ... };
class PackagedProduct : public Product { ... };
class FreshProduct : public Product { ... };
In Java, the vector (or list, container, ...) stores by-reference, not by-value. That's the crucial difference. In C++, this means using a smart pointer:
std::vector< std::shared_ptr< Product > > v;
v.push_back( std::make_shared< FreshProduct >( some args... ) );
You can use dynamic_pointer_cast once you retrieved the pointer back from the vector to check what object it is, but other options are available.
This is, of course, just a rough idea and you'll need to learn a lot about the details, shared_ptr, etc. but I hope you have enough keywords and ideas to google now :)
Create a Product object with the common fields and a PackagedProduct (extending Product) plus a FreshProduct (also extending Product) with their particular fields. Then store smart pointers to them in:
std::vector<std::unique_ptr<Product> > Vec;
If FredOverflow's suggestion of using two vectors doesn't suit your needs, an alternative could be to make a variant of the two types and keep a vector of variants. This is fairly easy to do using boost::variant. You can wrap the functionality you need in free functions or you can wrap the variant in a class. Here's an example
#include <boost/variant/variant.hpp>
#include <boost/variant/apply_visitor.hpp>
#include <boost/variant/static_visitor.hpp>
struct FreshProduct
{
double price;
bool needsRefrigeration;
};
struct PackagedProduct
{
double price;
};
struct VariantProduct
{
VariantProduct(const FreshProduct& p) : product(p) {}
VariantProduct(const PackagedProduct& p) : product(p) {}
double getPrice() const;
bool needsRefrigeration() const
{
struct helper : public boost::static_visitor<bool>
{
bool operator ()(const FreshProduct& product) const
{
return product.needsRefrigeration;
}
bool operator ()(...) const
{
return false;
}
};
return boost::apply_visitor(helper(), product);
}
private:
boost::variant<FreshProduct, PackagedProduct> product;
};
// in cpp
namespace {
struct GetPrice : public boost::static_visitor<double>
{
template <class T>
double operator ()(const T& product) const
{
return product.price;
}
};
} // anonymous namespace
double VariantProduct::getPrice() const
{
return boost::apply_visitor(GetPrice(), product);
}
The advantage of this approach is that you don't need to use dynamic allocation in order to keep a collection of fresh or packaged products. The downside is that it is not so easy to extend the types supported in the variant as when using inheritance.
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.
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).