1

I have multiple child classes inside a parent class vector where each child has it's own type. The parent has a virtual getType function and each child overrides it with it's own; not even sure if I need this TBH but I got from this Access child members within parent class, C++

When I loop over the vector (loop not shown here), the type is only that of the parent, since it's a vector of Parents, but I need the type of each individual child made with it's own constructor.

#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Parent 
{
  private:
  string type = "parent";
  public:
    virtual string getType()
    {
      return type;
    }

};
class Child1 : public Parent
{
  string type = "type1";
  public:
    string getType()
    {
      return type;
    }
};
class Child2 : public Parent
{
  string type = "type2";
  public:
    string getType()
    {
      return type;
    }
};
//main.cpp
int main()
{
   vector<Parent> children;
   Child1 t1;
   Child2  t2;
   children.push_back(t1);
   children.push_back(t2);
   //THIS WORKS
   cout << t1.getType(); // returns type1
   // I NEED THIS TO WORK
   cout << children[0].getType(); // returns parent. 
   cout << children[1].getType(); // I need type1 && type2

}

How can I do this? I have no way of knowing what type of child each is otherwise, or is there another way to do this?

Mote Zart
  • 861
  • 3
  • 17
  • 33

1 Answers1

6

You just experimented slicing.

A Child is a Parent plus a few more things.

When you try to put a Child in the vector of Parent, only the Parent part of each of these Children is put in the vector (because the vector does not contain Children but Parents); hence the name slicing.

In order to obtain the dynamic polymorphism you are looking for, you need a vector of pointers to Parent; this way, each pointed-to element can be whether a Parent or a Child and behave accordingly.

This is often done with a dynamic allocation of each element in the vector, but this is not mandatory. For example, you could store all the Parents in a vector, all the Children 1 in another, and so on, and finally use a vector of pointers to designate some of them in any order.

If you decide to allocate each Parent/Child individually, you should consider smart-pointers like std::unique_ptr<T> rather than raw pointers and new/delete.

You will find below your example slightly modified in order to obtain dynamic polymorphism. It relies on dynamic allocation of each element thanks to std::unique_ptr<T>/std::make_unique().

Note that, because of dynamic polymorphism, you need a virtual destructor (even if it does nothing special). Since this hierarchy of types is intended for dynamic polymorphism, it is encouraged to prevent slicing (that you have just experienced) by forbidding the use of copy/move operations. Thus, you have to provide one or several constructors that fulfil your needs (but this is quite common).

My last advice is « avoid dynamic polymorphism; prefer template » but it is another topic ;^)

/**
  g++ -std=c++17 -o prog_cpp prog_cpp.cpp \
      -pedantic -Wall -Wextra -Wconversion -Wno-sign-conversion \
      -g -O0 -UNDEBUG -fsanitize=address,undefined
**/

#include <iostream>
#include <string>
#include <vector>
#include <memory> // std::unique_ptr<T>, std::make_unique()

class Parent 
{
public:
  virtual ~Parent() =default; // ensure correct destruction

  // forbid copy and move in order to prevent slicing
  Parent(const Parent &) =delete;
  Parent &operator=(const Parent &) =delete;
  Parent(Parent &&) =delete;
  Parent &operator=(Parent &&) =delete;

  Parent() =default; // provide a suitable constructor

  virtual
  const std::string &
  getType() const
  {
    return type;
  }
private:
  // inline static // is probably better
  const std::string type{"parent"};
};

class Child1: public Parent
{
public:
  const std::string &
  getType() const override
  {
    return type;
  }

private:
  // inline static // is probably better
  const std::string type{"type1"};
};

class Child2 : public Parent
{
public:
  const std::string &
  getType() const override
  {
    return type;
  }

private:
  // inline static // is probably better
  const std::string type{"type2"};
};

int
main()
{
  const auto p=Parent{};
  std::cout << "p: " << p.getType() << '\n';
  const auto c1=Child1{};
  std::cout << "c1: " << c1.getType() << '\n';
  const auto c2=Child2{};
  std::cout << "c2: " << c2.getType() << '\n';
  auto people=std::vector<std::unique_ptr<Parent>>{};
  for(auto i=0; i<2; ++i)
  {
    people.emplace_back(std::make_unique<Parent>());
    people.emplace_back(std::make_unique<Child1>());
    people.emplace_back(std::make_unique<Child2>());
  }
  for(const auto &e: people)
  {
    std::cout << e->getType() << '\n';
  }
  return 0;
}
prog-fh
  • 13,492
  • 1
  • 15
  • 30
  • For more information on slicing in C++ see this [Q&A](https://stackoverflow.com/questions/274626/what-is-object-slicing). – davidbak Feb 07 '21 at 00:06