0

I'm designing a simple tracking program which builds a vector of pointers to objects which represent animals in a zoo.

I declared my vector as: vector<Animal*> zooAnimals, Animal* being a pointer to the Animal class.

The problem lies in the inheritance with child classes from Animal. For example, Animal is the parent for Mammal, which is in turn the parent for Bat. But when I create Bat* and add it to the vector, it doesn't pick up on all the other fields it was supposed to have inherited. It just picks up on the fields from Animal (the tracking number and animal name), but nothing from Mammal (type, subType, and nursingStatus).

The way I have it set up is that my program calls a function createNewZooAnimal() which creates and returns an Animal* (pointer to object of type animal), by (theoretically) calling the specific constructor for the animal's sub-type and returning a pointer to the newly created object (i.e., Crocodile* or Bat*, etc). However, these newly created pointers aren't inheriting from either Mammal/Oviparous or from the specific constructor for their own class. My header files do not seem to have any errors, nor the cpp file implementing them. The system happily creates the specifc pointers I need, but does not inherit anything beyond the most basic class Animal.

Animal* createNewZooAnimal(int trackingNum, string name, string type, string subType, int numEggs, int nursing) {
    
//using a simple if-else if structure here, we cover each sub-type, calling the constructor for each to create the pointer for each object

    if (subType == "Crocodile") {
        Crocodile* croc1 = new Crocodile(numEggs); //calls constructor and creates a pointer to the new object of class Crocodile
        croc1->trackingNum = trackingNum;
        croc1->name = name;
        return croc1; // returns pointer to get included in the vector
    }

    else if (subType == "Goose") {
        Goose* goose1 = new Goose(numEggs); //calls the constructor for Goose, passing in numEggs, and assigns a pointer to the location of the new object
        goose1->trackingNum = trackingNum;
        goose1->name = name;
        return goose1; //returns pointer to get included in the vector
    }

    else if (subType == "Pelican") { //constructs new pelican object and returns its pointer
        Pelican* pelican1 = new Pelican(numEggs);
        pelican1->trackingNum = trackingNum;
        pelican1->name = name;
        return pelican1;
    }


    else if (subType == "Bat") { //constructs new bat object and returns its pointer
        Bat* bat1 = new Bat(nursing);
        bat1->trackingNum = trackingNum;
        bat1->name = name;
        return bat1;
    }

    else if (subType == "Whale") { //constructs a new whale object and returns its pointer
        Whale* whale1 = new Whale(nursing);
        whale1->trackingNum = trackingNum;
        whale1->name = name;
        return whale1;
    }

    else if (subType == "SeaLion") {  //constructs a new sea lion object and returns it pointer
        SeaLion* seaLion1 = new SeaLion(nursing);
        seaLion1->trackingNum = trackingNum;
        seaLion1->name = name;
        return seaLion1;
    }

The way I have setup things is that the user will enter the info about the animal (name, tracking number, type (mammal/oviparous), sub-type (bat, whale, etc)...) and then pass that to the pointer creator createNewZooAnimal to call the specific constructor for the animal's sub-type. The function then returns a pointer to this object which is then pushed onto the vector:

cout << "Enter 'Y' or 'y' to save this animal to the system, or 'N' or 'n' to cancel: " << endl;
cin >> addAnimalConfirm;
  if (addAnimalConfirm == 'Y' || addAnimalConfirm == 'y') { // if user confirms addition of animal, parameters are passed
                                                            // to pointer return function, where a new animal object is created
                                                            // and we receive the pointer to it.  This is then pushed onto our vector
                                                            // of pointers
      Animal* temp = createNewZooAnimal(trackingNum, name, type, subType, numEggs, nursing);
      zooAnimals.push_back(temp);
      cout << "Animal added to Wildlife Zoo's system successfully." << endl;
  }

This is the Animal.h code:

class Animal{
public:
    std::string name;
    int trackingNum;

    Animal() {
        trackingNum = 0;
        name = " ";

    }
};

And the Mammal.h, for example:

class Mammal : public Animal {
public:
    Mammal();
    Mammal(int nursingIndicator);
    void displayAnimalData();
    std::string type;
    std::string subType;
    int getNursing();
    void setNursing(int numberEggs);

private:
    int nursing;
};

And Bat.h:

class Bat : public Mammal {
public:
    Bat();
    Bat(int nursingIndicator);
};
Wolf
  • 9,679
  • 7
  • 62
  • 108
faangorn
  • 57
  • 7
  • 2
    Please post a [mcve]. The problematic part seems to be " But when I create Bat* and add it to the vector, it doesn't pick up on all the other fields it was supposed to have inherited" but that part is not in the code you posted. What you did post looks like a factory function, and looks ok-ish – 463035818_is_not_an_ai Feb 22 '21 at 15:40
  • 3
    If you have a pointer to the base type to a more derived object, you can't use that pointer to access the more derived part of the object. If you have a `Animal*` all you know is that the pointed object is some form of `Animal` so that is all you can access, the `Animal` part of the object. It isn't clear what you want to achieve with your vector. The correct solution to this problem depends on what you are trying to do. – François Andrieux Feb 22 '21 at 15:42
  • 1
    More seriously, storing raw pointers could later lead to ownership problems, and you should considere using *smart* pointers (`unique_ptr` or `shared_ptr`). But they should not lead to object slicing. I assume that that slicing comes because or the way you use those pointers. – Serge Ballesta Feb 22 '21 at 16:10
  • Why do you think there is an issue? You don't show it in the question. Is it maybe that you observe memory leaks? I think that what you show of the construction process doesn't enable for correct deletion. The only way I see is through the pointers stored in `zooAnimals` and that will in fact do the wrong thing. – Wolf Feb 22 '21 at 20:37
  • @SergeBallesta Most probably, slicing isn't what OP is *complaining* about, sadly an actual access scenario is still not present in the question. – Wolf Feb 22 '21 at 20:40
  • 2
    Forget the vector. Make an Animal* that points to a Bat. Now, using the Animal* you can access any member function or data that Animal has, and if it is virtual it will use the derived class instead. If you need specific Bat things (like EchoLocate() or something) you need to know the object is a Bat before you can do that - so one of the virtual functions should be a "type" so that you know what kind of animal you are pointing to. Then you can create a Bat* that points to it and access whatever you want. – Jerry Jeremiah Feb 22 '21 at 20:45
  • Please fix the `createNewZooAnimal` function (maybe also shorten): the closing brace is missing and also the default return value ... that's the final else for the case of an unknown `subType`. – Wolf Feb 22 '21 at 20:52
  • @JerryJeremiah Absolutely (concerning `vector`)! But the `type` thing is still not very polymorphic, well, for the start it may help to familiarize with OOP but in the long term it sticks to the C-way of getting things done. – Wolf Feb 22 '21 at 20:59
  • @Wolf I know - I agree. But I'm not sure how to explain to him that he needs a way to know which derived class the pointer actually points to - because surely Bat will have specific functionality that other animals don't have. If he only uses functionality common to all animals then the accepted answer works well. Do you have a suggestion? I guess there is dynamic_cast but that isn't better really. – Jerry Jeremiah Feb 22 '21 at 21:09
  • Here is a bit of an example: https://onlinegdb.com/rkCXRi-fO – Jerry Jeremiah Feb 22 '21 at 22:20

2 Answers2

1

The right way would be to design the base class such that it covers the common interface of all Animals. You can have for example:

struct Animal {
    virtual void eat() = 0;
    virtual void sleep() = 0;
    virtual std::string getName() = 0;
    // ... more ...
};

That is what defines what Animals can do. Different subclasses can then provide different implementations of this interface. You can create a vector of (smart-)pointers to instances of sub-classes. They all can have different ways to sleep or to eat, but an Animal can only do what an Animal can do.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • Thank you, the project guide stated that the Animal class is supposed to be abstract but I am not sure how to make it so because there are no functions, just variables (name and trackingNumber) – faangorn Feb 22 '21 at 15:50
  • 2
    @faangorn a class is said to be abstract when it has at least one pure virtual method, in my example all methods are pure virtual. – 463035818_is_not_an_ai Feb 22 '21 at 15:51
  • So since name and trackingNum are private, I should be able to the set the accessors as virtual and the program will implement the specific constructor needed at run-time? – faangorn Feb 22 '21 at 15:54
  • 1
    this might help https://stackoverflow.com/questions/14631685/c-abstract-class-without-pure-virtual-functions – Alessandro Teruzzi Feb 22 '21 at 15:57
  • 2
    @faangorn I have the impression that you are currently trying to understand and do too much at once. Constructors arent "implemented at run-time". Actually I didnt do you a big favor by writing an answer before your question is clarified. I suggest you to go some steps back to a simpler example, perhaps a single sub-class. Then you can prepare a [mcve] and post it here. There is too much missing from the code you posted to give you a complete answer. – 463035818_is_not_an_ai Feb 22 '21 at 15:58
0

The common interface (base class) you built could be named "direct member access interface".

This is a possible, but a very limited approach, because the compiler implements the member access statically as it is using member offsets that are added to the base address of each object. Dynamic access is done via virtual method tables (VMT), these tables contain addresses of virtual functions, each object stores a pointer to the table of its actual class as a hidden member variable. As is normal for each member (hidden or not), derived classes place this member on the exact same offset, so derived classes are handled the same way. In other words, instances of the exact same class share the same VMT address.

The good answer by largest_prime_is_463035818 shows you a useful example of a dynamic common interface consisting of three pure virtual functions. To implement the behavior of actual types of animals, the corresponding classes have to provide an implementation for how they eat or sleep. The decision to make the getName() function pure virtual as well is more an esthetic one.

Looking onto your code, I'd say the void displayAnimalData(); function is a good candidate for being moved from Mammal to Animal and transformed into a (pure) virtual function. That way you could start exploring polymorphy without the need to change too much of what you have so far:

class Animal {

public:
    std::string name;
    int trackingNum;

    // a placeholder to be implemented by specific animal classes
    virtual void displayAnimalData()=0;    

    // this way we clean up the actual class through the common interface
    virtual ~Animal() {}

};

By providing a virtual destructor, we make it possible to dispose instances of the actual class (and doing their specific cleanup) by calling the destructor through the base class interface, i.e. if we have some of type (Animal* animal), delete animal will do the right thing, otherwise, at least memory leaks will appear. Of course, exposing public member variables can still be considered an issue here.

[Edit] reworked: virtual destructors in base classes are more essential than protected constructors.

Wolf
  • 9,679
  • 7
  • 62
  • 108