C++ struct with template in vector - c++

I'm making a text adventure game in C++. This is the struct for an object in that game (something the user can pick up and put in the inventory)
template <int N>
struct Object
{
static const int length = N;
string names[N];
string description;
};
Examples of objects:
Object<2> flower = { {"flower", "the flower"}, "A strange looking flower"};
Object<3> book = { { "the book", "book", "recipe book" }, "The world's finest cocktail recipes now all in one easy to use companion." };
The multiple names are synonyms for the command parser, so the user can input "pick up book" or "pick up recipe book", which in both cases picks up the object book.
Now I'd like to create a vector inventory to store all the elements in the inventory.
vector<Object> inventory;
Now, off course, this gives me a compiler error, because it expects something like this:
vector<Object<5>> inventory;
However, some objects have more names than others, is something like this possible and if so, how?
vector<Object<N>> inventory;

All your different Object<N> classes are different types with different sizes. You can't put them in a homogenous container.
You would need some base class or base interface, and store pointers in the vector, relying on virtual dispatch and polymorphism when you pull the elements out. This would make your container of Objects a heterogenous container.
Alternatively, and preferably, drop the template and store the names in a member container:
struct Object
{
set<string> names;
string description;
};
vector<Object> easy;
PS. I don't consider Object to be a good name for any class. CompuChip's suggestion of InventoryItem makes more sense.

A vector needs to know the size of the objects it contains, so obviously it cannot allow varying template instances inside (sizeof(Object<N+X>) > sizeof(Object<N>)).
Remove the templated array from the main Object struct, and replace it with a common vector or list or string objects instead, and your problem is solved.

Derive Object from BaseObject, and form a vector of smart pointers to BaseObject:
struct BaseObject
{
virtual ~BaseObject() = default;
};
template<int N>
struct Object : public BaseObject
{
static const int length = N;
string names[N];
string description;
};
typedef shared_ptr<BaseObject> Objptr;
vector<Objptr> Inventory(1006);

Related

A need for dynamic cast of a derived class: looking for an alternative approach

I present my question in this simple form:
class animal {
public:
animal() {
_name="animal";
}
virtual void makenoise(){
cout<<_name<<endl;
}
string get_name(){
return _name;
}
protected:
string _name;
};
class cat : public animal {
public:
cat() {
this->_name="cat";
}
};
class dog : public animal {
public:
dog() {
this->_name = "dog";
}
};
I want to store all animal types together in a single container such as:
vector<animal*> container;
barnyard.push_back(new animal());
barnyard.push_back(new dog());
barnyard.push_back(new cat());
At some point in my code, I need to convert a dog object into a cat object. And all I need from this converting is to set up a fresh dog object and replace it at the same index number as a cat counterpart was located. As I understood, dynamic_cast wouldn't work in this case and based on C++ cast to derived class, it's mentioned that such a conversion is not a good practice. Since cat and dog in my model have distinct behavioral properties, I don't want to put their definitions into the animal model. On the other hand, storing them separately in different vectors would be difficult to handle. Any suggestions?
You say:
I need to convert a dog object into a cat object.
But then:
And all I need from this converting is to set up a fresh dog object and replace it at the same index number as a cat counterpart was located.
Do you need to convert it or replace it?? That's a completely different operation.
To convert you need to setup a function that will take a dog and return a cat:
auto convertDogToCat(Dog const& dog) -> Cat {
auto cat = Cat{};
// fill cat's member using dog's values...
return cat;
}
But to replace simply reassign with a new one:
// v--- a cat is currently there
barnyard[ii] = new Dog{};
// ^--- we replace the old pointer
// with one that points to a dog.
But that creates a memory leak, to remove the leak, simply use std::unique_ptr:
#include <memory> // for std::unique_ptr
// The base class need a virtual destructor
class animal {
public:
virtual ~animal() = default;
// other members...
};
std::vector<std::unique_ptr<animal>> barnyard;
barnyard.emplace_back(std::make_unique<animal>());
barnyard.emplace_back(std::make_unique<dog>());
barnyard.emplace_back(std::make_unique<cat>());
barnyard[ii] = std::make_unique<Dog>();
Here’s an alternative approach. Doesn’t use OOP or dynamic dispatch, but provides equal functionality to your sample. Also much faster, because no dynamic memory is required to allocate/free, animals are single bytes.
enum struct eAnimalKind : uint8_t
{
Generic = 0,
Cat = 1,
Dog = 2,
};
string get_name( eAnimalKind k )
{
static const std::array<string, 3> s_names =
{
"animal"s, "cat"s, "dog"s
};
return s_names[ (uint8_t)k ];
}
void makenoise( eAnimalKind k )
{
cout << get_name( k ) << endl;
}
If your classes keep more state than a type, use one class with that enum as a member.
If some animals use custom set of fields/properties it gets tricky but still possible, nested structures for specie-specific state, and std::variant of these structures inside class animal to get track on the specie and keep the data. In this case you no longer need enum eAnimalKind, std::variant already tracks the type it contains.
Classic C++ OOP requires dynamic memory. Derived classes generally have different sizeof, you can’t keep them in a single vector you can only keep pointers, and in runtime you’ll hit RAM latency on accessing every single element.
If your animals are large and complex i.e. megabytes of RAM and expensive methods, that’s fine. But if your animals are small, contain a couple of strings/numbers, and you have a lot of them, RAM latency will ruin the performance of OOP approach.

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.

Object oriented design for hotel reservation system

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).

How to build a Constructor for a Vector of pointers to classes

I need to build a constructor for a Vector of pointers to classes...
My class is :
class Song {
string song_name;
string auther_name;
int popularity;
SongStructure song_format;
int song_length;
int lengths[size];
bool first_condition_true();
bool second_condition_true(int index);
}
};
My vector is :
vector<Song*> play_list;
With the new standard C++11 / 0x initializer list has been introduced:
I assume you wanted to create a SongBook class containing a vector of Song-Pointers and then additional Infos.
They can be used like this:
Class file:
class SongBook {
vector<Song*> songlist;
string name;
// Constructor
SongBook(std::initializer_list<Song*> songs) : songlist(songs) {}
}
and then call like this from your main for example
SongBook book({new Song(...), new Song(...), new Song(...)});
Your vector have a working constructor that you can use. (It create place for the pointers). What you are asking for is probably a way to make the pointers point to some valid Song. You will need to do that in some loop (STL "loop" probably). But you probably dont need that, becouse your Song is legaly copyable!?. You can use vector<Song>

Initialising a 2D vector with values at declaration

I'm currently working on a program which randomly generates items (e.g. weapons, armour etc.) and I want to make global constant vectors which hold all the names that could be given to the items. I want to have this 2D vector in a header file that's available to all my other classes (but not modifiable), so I need to initialise it at declaration.
I previously used the following:
static const std::string v[] =
{
"1.0", "1.1", "1.2", "null"
};
const std::vector<std::string> versions( v, v+sizeof( v)/sizeof( v[0]));
This worked for a 1D vector, however I want to use a 2D vector to store item names.
I have tried using the following however it means I don't have the member functions (such as size()):
static const std::string g_wn_a[] = { "Spear", "Lance", "Jouster" };
static const std::string g_wn_b[] = { "Sword", "Broadsword", "Sabre", "Katana" };
const std::string* g_weapon_names[] = { g_wn_a, g_wn_b };
I also don't want to use a class to store all the names because I feel it would be inefficient to have variables created to store all the names everytime I wanted to use them.
Does anyone know how I can solve my problem?
You could use a class with const static members. This way, your class would just behave like a namespace and you wouldn't have to create an instance of the name-holding class to use the names.
struct MyNames {
// const static things
const static string myweapon = "Katana"
};
string s = MyNames::myweapon; // s = "Katana"
This is C++, so the most common way to do this is to write a class that does this in its constructor, and then create a const object of that class. Your class would then provide various member functions to query the various items it maintains.
As a bonus, this will make it easier for the rest of your code to use the various items.