Reputation: 35
I have a superclass Entry
and subclasses MusicAlbum
, Book
and Film
. Instances of these subclasses are stored according to the name of the item ie Book1
. The name and type of all these instances are stored in a vector cat_vector
which is a vector of objects of class libCatalogue
which simply stores the name and type:
class libCatalogue{
std::string name;
std::string type;
public:
libCatalogue(std::string name, std::string type);
std::string getname();
std::string gettype();
};
libCatalogue::libCatalogue(std::string name, std::string type) :name(name), type(type) {};
std::vector <libCatalogue> cat_vector;
Entries in the vector are made in the constructor eg.
MusicAlbum::MusicAlbum(std::string a, std::string b, std::string borrower)
: name(a), artist(b), Entry(borrower){
cat_vector.push_back(libCatalogue(name, "MusicAlbum"));
Each subclass has a member function called printdetails()
. I want to use a loop to step through each entry in cat_vector
and print the details of the entry but the following does not work:
int no = 1;
for (auto it = begin(cat_vector); it != end(cat_vector); ++it)
{
std::string name_ = it->getname();
std::string type_ = it->gettype();
std::cout << "Entry no. " << no << std::endl;
std::cout << "Name: " << name_ << std::endl;
std::cout << "Type: " << type_ << std::endl << std::endl;
if (type_ == "MusicAlbum"){
name_.printdetails(); //print using MusicAlbum member function
}
//etc...
no++;
I know it is because name_
is a string and not an object of any of the classes I want to call, but I haven't been able to find any way to convert it so far. Is there any way to tell the compiler that name_
is referring to an object of one of the subclasses?
Upvotes: 3
Views: 2246
Reputation: 57678
To augment @abhijit's answer, I often use static tables if the content is small and the usage is few.
// Typedef for a the function pointer
typedef void (*Function_Pointer)(void);
struct key_function_entry
{
const char * key_text;
Function_Pointer function;
};
void Process_Foo1_Request(void);
void Process_Bach_Request(void);
void Process_Eat_Request(void);
static const key_function_entry delegation_table[] =
{
{"foo1", Process_Foo1_Request},
{"Bah", Process_Bah_Request},
{"eat", Process_Eat_Request},
};
static const unsigned int delegation_entries =
sizeof(delegation_table) / sizeof(delegation_table[0]);
void Process_Request(const std::string& request)
{
for (unsigned int i = 0U; i < delegation_entries; ++i)
{
if (request == delegation_table[i].key_text)
{
delegation_table[i].function(); // Execute the associated function.
break;
}
}
}
An advantage here is that the table is static (one instance) and constant, so it can be placed into read-only memory. The table doesn't need to be built during runtime (like a std::map
). The code references a table created during compilation phase. (It's an embedded systems thing, saving memory or placing stuff into read-only memory.)
For small number of entries, a linear search may be faster than a std::map
.
For larger entries or very large number of accesses, an std::map
may be preferred.
Upvotes: 0
Reputation: 63707
C++ is a statically typed compiled language.You cannot create variables on fly. Fortunately, for cases like these, the work around is to use a lookup table. Generally this is achieved through a map where the key would be the string and the value would be the function you would want to associate and call for the particular string.
I know it is because name_ is a string and not an object of any of the classes I want to call, but I haven't been able to find any way to convert it so far. Is there any way to tell the compiler that name_ is referring to an object of one of the subclasses?
when you qualify a member, the member name is qualified with respect to the type of the variable not with respect to the content. So the call name_.printdetails()
would mean you are trying to invoke the member function printdetails for the instance of type std::string but std::string does not have a member function named printdetails.
A simple example to extend the above idea
struct Spam
{
enum { NO_OF_FUNCTIONS = 4 };
Spam()
{
lookup_callback["Foo1"] = std::bind(&Spam::foo1, this);
lookup_callback["Foo2"] = std::bind(&Spam::foo2, this);
lookup_callback["Foo3"] = std::bind(&Spam::foo3, this);
lookup_callback["Foo4"] = std::bind(&Spam::foo4, this);
}
void foo1() { std::cout << "Foo1" << std::endl; }
void foo2() { std::cout << "Foo2" << std::endl; }
void foo3() { std::cout << "Foo3" << std::endl; }
void foo4() { std::cout << "Foo4" << std::endl; }
void call(std::string name)
{
if (lookup_callback.count(name) > 0)
{
lookup_callback[name]();
}
else
{
std::cerr << "Invalid Function Call" << std::endl;
}
}
std::map<std::string, std::function<void(void)>> lookup_callback;
};
// Driver program to test above functions
int main()
{
std::string name;
Spam spam;
for (std::cin >> name; name != "quit"; std::cin >> name)
{
spam.call(name);
}
}
Upvotes: 4
Reputation: 7616
If you pass an instance of Entry
around than you won't have a problem because you will be able to call:
it->entry->print_details();
If you don't want LibCatalogue
to be aware of instances of Entry
you can create a new class called Printable
or something similar. This class will be held by Entry
and by LibCatalogue
. The `Printable class will have all the details required to print. That way you could call both:
it->printable->print_details();
entry->printable->print_details();
Upvotes: 0