1

I have an exercise to practice multiple inheritance and polymorphism and something is not going well. The exercise includes 4 classes I need to build:

  1. Creature
  • char * name
  • int age
  • int numOfOffsprings
  1. Vampire : Creature
  • int lightSensitivity
  • Vampire** offsprings
  1. Werewolf : Creature
  • int humanHours
  • int werewolfHours
  • Werewolf** offsprings
  1. VampireWerewolf : Vampire, Werewolf
  • Creature** offsprings

There is a method called printInfo that prints out the info about each class. As you might expect, the use of the function is overridden in every class while also printing the info of the parents. When I try to use printInfo from VampireWerewolf I get the Creature part twice and I'm not sure what should I do to make it run once. Attaching the code below

Creature.h

#ifndef CREATURE_H
#define CREATURE_H
#include <iostream>
using namespace std;

class Creature {
protected:
    char* m_name;
    int m_age;
    int m_numOfOffsprings;
public:
    // Constructors
    Creature(): m_name(nullptr), m_age(0), m_numOfOffsprings(0) {}
    Creature(const char* name, int age, int numOfOffsprings):
        m_age(age),
        m_numOfOffsprings(numOfOffsprings) {
        m_name = new char[strlen(name) + 1];
        strcpy_s(m_name, strlen(name) + 1, name);
    }

    char* getName() { return m_name; }
    int getAge() { return m_age; }
    int getNumOfOffsprings() { return m_numOfOffsprings; }

    virtual void printInfo() {
        cout << "===Creature printInfo===" << endl << endl;
        cout << "Name: " << m_name << endl;
        cout << "Age: " << m_age << endl;
        cout << "Number of offsprings: " << m_numOfOffsprings << endl;
    }
};

#endif // !CREATURE_H

Vampire.h

#ifndef VAMPIRE_H
#define VAMPIRE_H
#include <iostream>
#include <array>
#include "Creature.h"
using namespace std;

class Vampire : public virtual Creature {
    int m_lightSensitivity;
    Vampire** m_offsprings;
public:
    // Constructors
    Vampire(): m_lightSensitivity(0), m_offsprings(nullptr) {}
    Vampire(char* name, int age, int numOfOffsprings, int lightSensitivity, Vampire** offsprings):
        Creature(name, age, numOfOffsprings), m_lightSensitivity(lightSensitivity) {
        if (numOfOffsprings <= 0) { m_offsprings = nullptr; }
        else {
            m_offsprings = new Vampire * [numOfOffsprings];
            for (int i = 0; i < numOfOffsprings; i++) {
                m_offsprings[i] = offsprings[i];
            }
        }
        
    }
    virtual void printInfo() override {
        Creature::printInfo();
        cout << "===Vampire printInfo===" << endl << endl;
        cout << "Light sensitivity: " << m_lightSensitivity << endl;
        for (int i = 0; i < m_numOfOffsprings; i++) {
            cout << "- Offspring " << i + 1 << ": " << m_offsprings[i]->m_name << endl;
        }
    }
};

#endif // !VAMPIRE_H

Werewolf.h

#ifndef WEREWOLF_H
#define WEREWOLF_H
#include <iostream>
#include "Creature.h"
using namespace std;

class Werewolf: public virtual Creature {
    int m_werewolfHours, m_humanHours;
    Werewolf** m_offsprings;
public:
    // Constructors
    Werewolf() : m_werewolfHours(0), m_humanHours(0), m_offsprings(nullptr) {}
    Werewolf(char* name, int age, int numOfOffsprings, int werewolfH, int humanH, Werewolf** offsprings):
        Creature(name, age, numOfOffsprings),
        m_werewolfHours(werewolfH),
        m_humanHours(humanH) {
        if (numOfOffsprings <= 0) { m_offsprings = nullptr; }
        else {
            m_offsprings = new Werewolf * [numOfOffsprings];
            for (int i = 0; i < numOfOffsprings; i++) {
                m_offsprings[i] = offsprings[i];
            }
        }
    }
    
    virtual void printInfo() override {
        Creature::printInfo();
        cout << "===Werewolf printInfo===" << endl << endl;
        cout << "Werewolf hours: " << m_werewolfHours << endl;
        cout << "Human hours: " << m_humanHours << endl;
        for (int i = 0; i < m_numOfOffsprings; i++) {
            cout << "- Offspring " << i + 1 << ": " << m_offsprings[i]->m_name << endl;
        }
    }
};

#endif // !WEREWOLF_H

VampireWerewolf.h

#ifndef VAMPIRE_WEREWOLF_H
#define VAMPIRE_WEREWOLF_H
#include <iostream>
#include "Vampire.h"
#include "Werewolf.h"
using namespace std;

class VampireWerewolf : public Vampire, public Werewolf {
    Creature** m_offsprings;
public:
    VampireWerewolf(
        char* name,
        int age,
        int numOfOffsprings,
        int lightSensitivity,
        int werewolfHours,
        int humanHours,
        Creature** offsprings):
        Creature(name, age, numOfOffsprings),
        Vampire(name, age, numOfOffsprings, lightSensitivity, nullptr),
        Werewolf(name, age, numOfOffsprings, werewolfHours, humanHours, nullptr) {
        if (numOfOffsprings <= 0) { m_offsprings = nullptr; }
        else {
            m_offsprings = new Creature * [numOfOffsprings];
            for (int i = 0; i < numOfOffsprings; i++) {
                m_offsprings[i] = offsprings[i];
            }
        }
    }

    virtual void printInfo() {
        Vampire::printInfo();
        Werewolf::printInfo();
                // <Additional VampireWerewolf info below>
    }
};

#endif // !VAMPIRE_WEREWOLF_H

Main.cpp

#include <iostream>
#include "Creature.h"
#include "Vampire.h"
#include "Werewolf.h"
#include "VampireWerewolf.h"
using namespace std;

int main() {
    VampireWerewolf ackbar((char*)"Ackbar", 100, 0, 50, 24, 0, nullptr);
    ackbar.printInfo();
    return 1;
}

I used virtual signature for the class and the method and also added the override keyword to Vampire::printInfo() and Werewolf::printInfo().

10
  • 3
    Side note: nothing in this code needs the extra stuff that std::endl does. Use '\n' to end a line unless you have a good reason not to. Commented May 12, 2024 at 21:09
  • 4
    Re: ackbar((char*)"Ackbar" -- don't do that. The problem is that the constructor for VampireWerewolf` takes char* when it should take a const char*. A cast is hardly ever the right answer to a problem like this. Commented May 12, 2024 at 21:12
  • You're right to make Creature a virtual base of Werewolf and Vampire. Unfortunately, that makes some things complicated. The constructors of VampireWerewolf, Werewolf, and Vampire handle the problem correctly for construction: Creature only gets constructed once. Similarly, its destructor will only run once. But you're on your own for any other member functions; you have to have some logic to recognize that the base class has already been printed so that it doesn't get printed again. Commented May 12, 2024 at 21:15
  • 1
    using namespace std; is not a good idea in source files. Doing it in a header file is much worse. Commented May 12, 2024 at 21:17
  • 2
    Prefer to use std::string for names and texts; pointers to characters are messy. Commented May 12, 2024 at 21:31

3 Answers 3

1

I found a way around it by dividing printInfo into two different functions: printCommonInfo that runs exclusively from Creature and prints name, age and numOfOffsprings and printSpecificInfo that runs from both Vampire and Werewolf to print only their respective unique members. When I run printInfo from VampireWerewolf, there is a single call to printCommonInfo and I called Vampire::printSpecificInfo() and Werewolf::printSpecificInfo().

Creature:

void printCommonInfo() {
    cout << "===Creature printInfo===" << endl;
    cout << "Name: " << m_name << endl;
    cout << "Age: " << m_age << endl;
    cout << "Number of offsprings: " << m_numOfOffsprings << endl;
}

virtual void printSpecificInfo() = 0;

void printInfo() {
    printCommonInfo();
    printSpecificInfo();
    cout << endl;
}

Vampire:

virtual void printSpecificInfo() override {
    cout << "===Vampire printInfo===" << endl;
    cout << "Light sensitivity: " << m_lightSensitivity << endl;
    if (m_offsprings != nullptr) {
        for (int i = 0; i < m_numOfOffsprings; i++) {
            cout << "- Offspring " << i + 1 << ": " << m_offsprings[i]->m_name << endl;
        }
    }
}

Werewolf:

virtual void printSpecificInfo() override {
    cout << "===Werewolf printInfo===" << endl;
    cout << "Werewolf hours: " << m_werewolfHours << endl;
    cout << "Human hours: " << m_humanHours << endl;
    if (m_offsprings != nullptr) {
        for (int i = 0; i < m_numOfOffsprings; i++) {
            cout << "- Offspring " << i + 1 << ": " << m_offsprings[i]->m_name << endl;
        }
    }
}

VampireWerewolf:

virtual void printSpecificInfo() override {
    Vampire::printSpecificInfo();
    Werewolf::printSpecificInfo();
    cout << "===VampireWerewolf printInfo===" << endl;
    for (int i = 0; i < m_numOfOffsprings; i++) {
        cout << "- Offspring " << i + 1 << endl;
        cout << "Name: " << m_offsprings[i]->getName() << endl;
        cout << "Species: ";
        if (auto* VOffspring = dynamic_cast<Vampire*>(m_offsprings[i])) {
            cout << "Vampire" << endl;
        }
        else if (auto* WOffspring = dynamic_cast<Werewolf*>(m_offsprings[i])) {
            cout << "Werewolf" << endl;
        }
        else if (auto* VWOffspring = dynamic_cast<VampireWerewolf*>(m_offsprings[i])) {
            cout << "Vampire Werewolf" << endl;
        }
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

Did you read and understand the comments my colleagues wrote? Those are very good questions, advices & lessons they wrote that you should incorporate in your project. In general, finding hacky or non-best-practice workarounds instead of doing it properly will result in "magical" code that can either work or it might fail randomly (and this is the best case scenario) or result into undefined behavior and crash with cryptic crash dumps (worse case scenario). All of this will end up in you wasting countless days debugging errors. It's just advice (that I wish someone gave me) for future projects.
1

I would design things quite differently.

Firstly, I would avoid having raw pointers for managing strings, and use std::string.

Second, I would introduce a base class (say) WithOffspring, and have it manage astd::vector<std::shared_ptr<WithOffspring> > (not a pointer to a pointer!) and use it to maintain the collection of offspring. I would impose no constraint on the type or number of offspring (that can be determined by derived classes, if needed). It would also allow multiple parents of any type (say, a Creature descended from two other Creatures of different type).

So the WithOffspring class would look something like

 class WithOffspring
 {
       std:string m_name;
       std::vector<std::shared_ptr<WithOffspring> > m_offspring;

     public:

       virtual void PrintInfo() const = 0;
 };

and then define WithOffspring::PrintInfo() [Yes, this is allowed, and is useful in this case].

void WithOffspring::PrintInfo()
{
    // print name and age

    for (const auto &child : m_offspring)
        if (child.get() != nullptr) child->PrintInfo();
}

There is no need to have a member int m_numOfOffsprings since this may be obtained as m_offspring.size().

Then I would create classes to manage attributes of each particular type of Creature.

 class WerewolfAttributes
 {
     // private members

     public:
         // constructors, etc

        void PrintInfo() const
        {
           // print werewolf attributes
        };

        // other operations/functions/behaviours relevant to a werewolf

 };

 class VampireAttributes
 {
     public:
        // constructors, etc

        void PrintInfo() const
        {
           // print vampire attributes
        };

        // other operations/functions/behaviours relevant to a vampire
 };

Then Creature would be a templated class, derived from WithOffspring.

 template<class Attributes> Creature : public WithOffspring
 {
     private:

        Attributes   m_attrib;

     public:

        Creature(const Attributes &a): WithOffSpring(), m_attrib(a) {}

        void PrintInfo() const
        {
             WithOffspring::PrintInfo();
             m_attrib.PrintInfo(); 
        };
 };

Note that the PrintInfo() function prints the info from the base class (exactly once, which avoids duplication) then the info related to its attributes.

Then individual types of creatures can be specialisations of Creature (assuming Creature provides everything needed for all creature types in its interface).

 using Vampire = Creature<VampireAttributes>;
 using Werewolf = Creature<WereWolfAttributes>;

To handle hybrids, I would create a HybridAttributes class

 template<class AttribA, class AttribB> class HybridAttributes
 {
     private:

         AttribA  parent_a;
         AttribA  parent_b;

     public:

         HybridAttributes(const AttribA &a, const AttribB &b) : parent_a(a), parent_b(b) {}
         
         void PrintInfo() const
         {
             parent_a.PrintInfo()
             parent_b.PrintInfo();
         };
 };

and also create hybrids as specialisations of Creature.

 using WerewolfVampireHybrid = Creature<HybridAttributes<WerewolfAttributes, VampireAttributes> >;

If there is a need for a hybrid of more than two creature types (say, the offspring of two different types of hybrid) then HybridAttributes can be easily modified to use a variadic template (parameter pack)

Creating any creature then has essential steps of create/populate the object representing attributes, then use it to create the particular creature.

   int main()
   {
       VampireAttributes Selene_attributes;
           // populate the attributes
       Vampire Selene(Selene_attributes);

       WerewolfAttributes William_attributes;
           // populate the attributes
       Werewolf William(William_attributes);

       WerewolfVampireAttributes Michael_attributes;
           // populate the attributes
       WerewolfVampireHybrid  Michael(Michael_attributes);
       
       // whatever
  }

Note that

  • There is no multiple inheritance, so no need for virtual bases.
  • The only class with a pure virtual function is WithOffspring (although that function is defined to provide functionality used by derived classes). All other classes are concrete.

Comments

-1

Just remove the line Creature::printInfo();

explanation: by invoking Creature::printInfo() inside overriden printInfo you are calling the base class function inside the overriden function, so it is printing twice

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.