3

I have several classes that inherit from one main class. For the sake of simplicity, I have over-simplified the class definitions to make it short and direct to the point.

animal.h

main class which all other classes inherit from:

class Animal {
protected:
    string name;
public:
    Animal(string name);
    virtual string toString() { return "I am an animal"; }
};

bird.h

class Bird: public Animal {
private:
    bool canFly;
public:
    Bird(string name, bool canFly = true) 
     : Animal(name)   // call the super class constructor with its parameter
    {
        this->canFly = canFly;
    }
    string toString() { return "I am a bird"; }
};

indect.h

class Insect: public Animal {
private:
    int numberOfLegs;
public:
    Insect(string name, int numberOfLegs) : Animal(name) {
        this->numberOfLegs = numberOfLegs;
    }
    string toString() { return "I am an insect."; }
};

Now, I need to declare a vector<Animal> that will hold several instances of each inherited class.

main.cpp

#include <iostream>
#include "animal.h"
#include "bird.h"
#include "insect.h"

// assume that I handled the issue of preventing including a file more than once
// using #ifndef #define and #endif in each header file.

int main() {

    vector<Animal> creatures;

    creatures.push_back(Bird("duck", true));
    creatures.push_back(Bird("penguin", false));
    creatures.push_back(Insect("spider", 8));
    creatures.push_back(Insect("centipede",44));

    // now iterate through the creatures and call their toString()

    for(int i=0; i<creatures.size(); i++) {
        cout << creatures[i].toString() << endl;
    }
}

I expected the following output:

I am a bird

I am a bird

I am an insect

I am an insect

but I got:

I am an animal

I am an animal

I am an animal

I am an animal

I know this has to do with the line 'vector creatures;. It is calling the constructor for Animal. But my intention is to tell the compiler, that this creaturespoints to an array ofAnimalinherited classes, might beBirdmight beinsect, the point is: they all implement their own unique respective version of toString()`.

What can I do to declare a polymorphic array of objects that are inherited from the same ancestor?

Community
  • 1
  • 1
Ahmad
  • 12,336
  • 6
  • 48
  • 88

3 Answers3

5

You cannot use a value semantic (read about object slicing). You must use pointers.

Example:

#include <iostream>
#include <memory>
#include <string>
#include <vector>

class Animal
{
 protected:
  std::string name;

 public:
  Animal(std::string name) : name(name)
  {
  }
  virtual std::string toString()
  {
    return "I am an animal";
  }
  virtual ~Animal()
  {
  }
};

class Bird : public Animal
{
 private:
  bool canFly;

 public:
  Bird(std::string name, bool canFly = true) : Animal(name)  // call the super class constructor with its parameter
  {
    this->canFly = canFly;
  }
  std::string toString()
  {
    return "I am a bird";
  }
};

class Insect : public Animal
{
 private:
  int numberOfLegs;

 public:
  Insect(std::string name, int numberOfLegs) : Animal(name)
  {
    this->numberOfLegs = numberOfLegs;
  }
  std::string toString()
  {
    return "I am an insect.";
  }
};

int main()
{
  std::vector<std::unique_ptr<Animal>> creatures;

  creatures.emplace_back(new Bird("duck", true));
  creatures.emplace_back(new Bird("penguin", false));
  creatures.emplace_back(new Insect("spider", 8));
  creatures.emplace_back(new Insect("centipede", 44));

  // now iterate through the creatures and call their toString()

  for (int i = 0; i < creatures.size(); i++)
  {
    std::cout << creatures[i]->toString() << std::endl;
  }
}

prints:

I am a bird
I am a bird
I am an insect.
I am an insect.

I also recommend reading about Sean parent Run Time Polymorphism. The idea is as follows:

#include <iostream>
#include <memory>
#include <string>
#include <vector>

class Animal
{
 public:
  struct Interface
  {
    virtual std::string toString() const = 0;
    virtual ~Interface()                 = default;
  };
  std::shared_ptr<const Interface> _p;

 public:
  Animal(Interface* p) : _p(p)
  {
  }
  std::string toString() const
  {
    return _p->toString();
  }
};

class Bird : public Animal::Interface
{
 private:
  std::string _name;
  bool        _canFly;

 public:
  Bird(std::string name, bool canFly = true) : _name(name), _canFly(canFly)
  {
  }
  std::string toString() const override
  {
    return "I am a bird";
  }
};

class Insect : public Animal::Interface
{
 private:
  std::string _name;
  int         _numberOfLegs;

 public:
  Insect(std::string name, int numberOfLegs)
      : _name(name), _numberOfLegs(numberOfLegs)
  {
  }
  std::string toString() const override
  {
    return "I am an insect.";
  }
};

int main()
{
  std::vector<Animal> creatures;

  creatures.emplace_back(new Bird("duck", true));
  creatures.emplace_back(new Bird("penguin", false));
  creatures.emplace_back(new Insect("spider", 8));
  creatures.emplace_back(new Insect("centipede", 44));

  // now iterate through the creatures and call their toString()

  for (int i = 0; i < creatures.size(); i++)
  {
    std::cout << creatures[i].toString() << std::endl;
  }
}
Picaud Vincent
  • 10,518
  • 5
  • 31
  • 70
3

Problem is with creatures.push_back(Bird("duck", true)); You are creating a Bird object and copying that in the Animal object. One way is to create objects dynamically so that correct function call can resolve using vtable. Modify this part of your code and it will work fine.

vector<Animal *> creatures;

    creatures.push_back(new Bird("duck", true));
    creatures.push_back(new Bird("penguin", false));
    creatures.push_back(new Insect("spider", 8));
    creatures.push_back(new Insect("centipede",44));

Edit: Make sure to release the memory before creatures goes out of scope.

The Philomath
  • 954
  • 9
  • 15
  • Since the pointers are created using `new` they have to be deallocated with `delete` once you are done with the vector. However, using a smart pointer like `unique_ptr` or `shared_ptr` avoids manual memory management. – jignatius Nov 24 '19 at 08:30
  • @jignatius Yes you have to. Since the problem is not tagged with C++11 or later tags I can't suggest a solution using smart pointer. – The Philomath Nov 24 '19 at 08:31
  • Ok. I just thought I would point that to the OP. – jignatius Nov 24 '19 at 08:36
1

C++ objects are values with specific types. This differs from many languages where variables always hold references to objects, so they can hold references to derived objects just as easily.

If you copy an instance if a derived class onto an obhect of a base class, you get slicing: only the base class data is copied, and the type of the assignee is still that of the base class.

To achieve polymorphic behaviour in C++ you need to either use std::variant to specify the allowed possibilities, in which case the object will hold one of the options, and will switch type between them when assigned to, or you need to use a pointer to the base class, which can hold a pointer to any derived type, but you must then be wary of memory leaks. You do need to use std::visit or std::get to retrieve the values though.

If you are going to use pointers you should always use std::shared_ptr or std::unique_ptr to manage the objects in order to avoid memory leaks.

Code with variant:

int main() {

    vector<std::variant<Bird,Insect>> creatures;

    creatures.push_back(Bird("duck", true));
    creatures.push_back(Bird("penguin", false));
    creatures.push_back(Insect("spider", 8));
    creatures.push_back(Insect("centipede",44));

    // now iterate through the creatures and call their toString()

    for(int i=0; i<creatures.size(); i++) {
        cout << std::visit([](auto const& creature){
            return creature.toString();
        },creatures[i]) << endl;
    }
}

Code with pointers:

int main()
{
  std::vector<std::unique_ptr<Animal>> creatures;

  creatures.emplace_back(std::make_unique<Bird>("duck", true));
  creatures.emplace_back(std::make_unique<Bird>("penguin", false));
  creatures.emplace_back(std::make_unique<Insect>("spider", 8));
  creatures.emplace_back(std::make_unique<Insect>("centipede", 44));

  // now iterate through the creatures and call their toString()

  for (int i = 0; i < creatures.size(); i++)
  {
    std::cout << creatures[i]->toString() << std::endl;
  }
}

Code with std::shared_ptr is equivalent: just replace unique with shared everywhere.

Anthony Williams
  • 66,628
  • 14
  • 133
  • 155