Home How do I store and use different classes in a vector in c++
Reply: 3

How do I store and use different classes in a vector in c++

Fyrebend
1#
Fyrebend Published in 2018-02-12 19:30:37Z

I just want to start by saying I'm still decently new to coding, and I've been trying to do research but just got stuck, as I can't find exactly what I am looking for.

I am trying to make my first game, a simple text adventure game. I was trying to make a player's inventory so I started using a vector of custom classes. After some research I found that I needed to make a base class of multiple classes.

Just for reference, I am using

vector<item*> inventory;

class Map : public item
{
public:
    void print();
    virtual string getName(){return name;};
private:
    string name = "Map";
}; 


class Compass : public item
{
public:
    virtual string getName(){return name;};
private:
    string name = "Compass";
};


class item
{
public:
    virtual string getName(){return name;};
private:
    string name = "item";
};

this is in my header file, and I have cpp files that expand on it.

When storing it I found out that I have to add virtual functions to get the name, so I added a name data member and made a getName() function and made it virtual. Eventually I was able to pull the items out and get the name, but then I found a new problem I couldn't find the answer to.

EDIT:

after using inventory.push_back(&map), trying to call (inventory[0])->print() gives me the error of "no member of print in item"

end edit

My map class has a print() function, and i need to use it from the vector. Would I have to create a virtual print() function in my item class just so it can transfer down using the virtual part, or is there an easier way of doing this?

sorry if this is simple or a stupid fix, when I was doing research all I found was people trying to find names and things, but i couldn't find how to access a method without having it in the base class.

DOUGLAS O. MOEN
2#
DOUGLAS O. MOEN Reply to 2018-02-14 17:36:07Z

i have to add virtual functions to get the name, so i added a name data member and made a getName() function and made it virtual. eventually i was able to pull the items out and get the name

  • I know polymorphic access can work for you (even though you provided scant info). I can recommend polymorphic access as a proven technique.

I have used it in several systems, and the idea saved lots of coding effort. Below I try to show how.


However, two wrongs do not make a right.

  • I strongly discourage getters or setters.

These are time wasters, your time, not run time, and there are several articles you should be able to find to help you make up your mind about them. My mind is made up, and I almost never use one. An article I like is "http://www.yegor256.com/2014/09/16/getters-and-setters-are-evil.html"

  • I strongly recommend data attributes be private. This idea is part of encapsulation.

I have learned that encapsulation saves time (again...your time, not run-time). You might research why encapsulation is part of OOP, and thus part of C++.

IMHO - getters and setters and 'public' data attributes simply defeat encapsulation. But more important, they waste your time.


would i have to create a virtual print() function in my item class just so it can transfer down using the virtual part, or is there an easier way of doing this?

  • Yes, but 'print' is an inappropriate method name for several reasons. Mostly it is insufficiently abstract. What are you printing?

With practice you will learn to specify an interface method name which is more abstract (than print) and yet not too abstract. The true effort of polymorphism is finding the appropriate abstraction.

I strongly prefer the polymorphic approach. IMHO, you will find no easier way to achieve what you need. You do need to practice your 'abstraction' levels.


Example:

First of all, this interface class seems like an ok starting point to me, (except for the use of getter/setter).

class item
{
public:
    // virtual string getName(){return name;}  
    // getters are programmer time waster
private:
    string name = "item";
};

You leave it to us to speculate about why you want to getName()? Can you answer this? Consider writing an [MCVE], please include enough to reveal how the calling code would use the getter.

For now, I will guess that 'name' will be used as output to a user, perhaps as part of some report (since it is a string). My question changes to: "What is the function name in which your getter is invoked?"

Let me offer a slightly more abstract method name, one that I have used in production code ... "showInventory()". This method is invoked as a part of the software response to an operator command (see user's manual ;).

  • In embedded systems, the inventory of each 'item' will be different. I have worked with inventories with few fields, and some with more than 40 fields.

I am sure you do not want to create 10+ getter methods. So I recommend you consider something like the following.

class item
{
public:
    virtual string showInventory(std::stringstream& ss) = 0;  // **not** print
    // contribute to Inventory Report
private:
    string name = "item";
};

Yes, every derived class must implement showInventory() to encode and describe all the unique issues associated with that derived class (10 fields? 40 fields?).

  • ONE showInventory() method can handle all N inventory fields needed for a derived item instance.

The alternative you seem to be heading down is N virtual getters, a much bigger effort.

  • This abstraction (to tell the object to showInventory() ) combined with using polymorphism for an easy mechanism to do the telling, is the powerful programmer-time-saving 'stuff'.

Here is a limited example of what pure virtual looks like, and how you use polymorphism to report an object instance inventory.

Note: In this form, 'class item' can not be instantiated. But Compass can. And Compass::showInventory() can invoke base class methods .. so perhaps 'item' can provide useful formatting, code, etc.

class item
{
public:
   item() = default;
   virtual ~item() = default;

   // virtual std::string getName(){return name;};  no, do not use
   // virtual void print() {...}                    no, too general

   virtual void showInventory(std::stringstream& ss) = 0; // ADDED
   // an abstraction close to useful and time saving

private:
   std::string name = "item";
};

// skip a few classes

class Compass : public item
{
public:
   // virtual std::string getName(){return name;}; do not use

   virtual void showInventory(std::stringstream& ss)
      {
         item::showInventory(ss); // show class hierarchy 
         ss << "  " << name;      // show this inventory
         // perhaps all 40 fields of inventory are streamed here
      }
private:
   std::string name = "\n  Compass";
};

Somewhere else, you are allowed to provide an implementation of the otherwise pure-virtual method.

void item::showInventory(std::stringstream& ss) 
{
   ss << "\n  " << name;

  //  << ...  and whatever else needed for this inventory
  //  << ... 40+ fields?
}

This does not allow an instantiation of 'item', just makes the base class item::showInventory() method 'callable' from derived classes as I showed in Compass::showInventory() above.


I have compiled something close to your code, and thus:

int main(int , char** )
{
   // item    i;  pure virtual can not be instantiated

   Compass c;

   std::stringstream ss1;
   c.showInventory(ss1);
   std::cout << ss1.str() << std::endl;

   return 0;
}

Only 3 lines to report a Compass instance inventory. (If this inventory had 10 fields, how many lines would it take using getters?)

With output

item  
Compass

Summary:

getter usage: imagine the method you used to call getName() is foo;

 // see   vector<item*> inventory;

InventoryReport::foo()
{
   std::stringstream ssFoo;

   for (auto it : inventory) {
       std::string name = it->getName(ss);       // foo uses name to decide additional fields?
       handleName(ssFoo);             // each unique hardware has unique inventory
       F1_t  field1 = it->getF1(); handle(ssFoo, f1);
       F2_t  field2 = it->getF2(); handle(ssFoo, f2);
       F3_t  field3 = it->getF3(); handle(ssFoo, f3);
       F4_t  field4 = it->getF4(); handle(ssFoo, f4);
       F5_t  field5 = it->getF5(); handle(ssFoo, f5);
       F6_t  field6 = it->getF6(); handle(ssFoo, f6);
       F7_t  field7 = it->getF7(); handle(ssFoo, f7);
       F8_t  field8 = it->getF8(); handle(ssFoo, f8);
       F9_t  field9 = it->getF9(); handle(ssFoo, f9);
       // handle() methods do formatting and spacing 
   }
   std::cout << ssFoo.str() << std::endl; // output 10 field report 
}

How get other inventory fields of each element? Is name all there is, or does name select another inventory report function? 10 inventories each with 10 fields?


Now using the abstraction:

InventoryReport::showInventory()
{
   std::stringstream ss;

   for (auto it : inventory)
       it->showInventory(ss);  // variable inventory fields
                               // only 1 method invocation .

   std::cout << ss.str() << std::endl;
}

See article "https://pragprog.com/articles/tell-dont-ask"

  • Procedural code gets information then makes decisions. Object-oriented code tells objects to do things. — Alec Sharp
Davislor
3#
Davislor Reply to 2018-02-13 05:55:47Z

In order to use any subclass of a base class polymorphically, you would pass around a pointer or reference to the base class, and call its virtual member functions through that.

You should carefully consider alternatives, such as storing Plain-Old-Data in your vector instead of a class for everything. How many methods will actually have a different implementation between subclasses?

One data structure you could use would be a std::vector<std::unique_ptr<item> >. You would create items such as std::unique_ptr<item>(static_cast<item*>(new compass)), or as a shortcut, inventory.emplace_back(...). This handles the memory management automatically. You can then call methods such as inventory[0]->getname();. But, be sure the destructor ~item() is virtual, even if it’s just virtual ~item() = default;. Otherwise, the wrong destructor would get called!

Another alternative would be std::variant, the STL replacement for tagged unions.

DOUGLAS O. MOEN
4#
DOUGLAS O. MOEN Reply to 2018-02-16 14:45:10Z

In my first answer, I attempted to illustrate how you can use polymorphism to report your inventory.

From different viewpoint, I can interpret your questions as, "can I simplify" this effort. And yes, you can. Software is remarkably flexible.

If we assume that the name string is a (very) simple inventory, we can still do something useful.

would i have to create a virtual print() function

Without polymorphism, you do not need to use virtual inheritance to fetch the name string (but it is still available for other purposes).

Consider the following code. Is it simpler?

The essential idea here is that the data item "name" exists only in the base class. The derived classes pass the name to the base class during the construction sequence (Compass ctor starts first, and the base class ctor finishes first.)

  class item
  {
  public:
     item() = delete;   // disallow the default ctor
     ~item() = default;

     item (std::string a_name) // pass by value from derived classes
        : name (a_name)
        {
        }

     std::string getName(){return name;};  // no virtual needed - this will work fine

     void showInventory(std::string& s) // no virtual needed, possibly a better abstraction
        {
           s = name;
        }
  private:
     std::string name; // empty string, the derived class provides value thru ctor
  };

  class Compass : public item
  {
  public:
     Compass(std::string a_name)
        : item(a_name)
        {
        }

     // std::string getName(){return name;};  // no need, see item

     // void showInventory(std::stringstream& ss) // not needed, see item
  private:
     // std::string name = "\n  Compass"; // not used, see item
  };

  int main(int , char** )
  {
     //item()  i; // error: use of deleted function ‘item::item()’

     item      i("  my name is item");  // a useless? item can be instantiated

     std::cout << "\n  Item.getName(): " << i.getName();

     // pointer suitable for inventory vector
     Compass* c1 = new Compass("    Compass 113");
     // auto var can be useful
     Compass  c2 ("    Compass 444");

     std::stringstream ss1;
     {
        std::string s1;
        c1->showInventory(s1);
        ss1 << "\n   c1->getName(): " << c1->getName();
        ss1 << "\n showInventory(): " << s1;

        ss1 << "\n";

        std::string s2;
        c2.showInventory(s2);
        ss1 <<"\n    c2.getName(): " << c2.getName();
        ss1 << "\n showInventory(): " << s2;
     }
     std::cout << ss1.str() << std::endl;

     return 0;
  }

A derived class can directly access the base class name, if desired, just change from private to protected.


example output:

      Item.getName():   my name is item
   c1->getName():     Compass 113
 showInventory():     Compass 113

    c2.getName():     Compass 444
 showInventory():     Compass 444
You need to login account before you can post.

About| Privacy statement| Terms of Service| Advertising| Contact us| Help| Sitemap|
Processed in 0.301351 second(s) , Gzip On .

© 2016 Powered by mzan.com design MATCHINFO