Bilderweast
Bilderweast

Reputation: 35

C++ use string to call object member function

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

Answers (3)

Thomas Matthews
Thomas Matthews

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

Abhijit
Abhijit

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

Benjy Kessler
Benjy Kessler

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

Related Questions