Heath
Heath

Reputation: 21

How can I make a class that inherits any one child of a particular base class

I apologize if this has been answered before but I've been looking for hours. Here is a contrived example of what I am trying to do.

#include <iostream>

using namespace std;

int main(void);

class humanoid {
public:
        int humanoid_attribute;
        virtual void do_something_to_any_humanoid(void) = 0;
};

class man: public humanoid {
public:
        int man_attribute;
        void do_something_to_any_humanoid(void) { cout << "Doing something to a man which is a humanoid.\n"; }
};

class cat {
public:
        int cat_attribute;
        virtual void do_something_to_any_cat(void) = 0;
};

class lion : public cat {
public:
        int lion_attribute;
        void do_something_to_any_cat(void) { cout << "Doing something to a lion which is a cat.\n"; }
};

class sphinx : public humanoid, public cat {
public:
        int sphinx_attribute;
        sphinx(humanoid & humanoidIn, cat & catIn) : humanoid(humanoidIn), cat(catIn) { }
};

int main(void) {
        man myMan;
        lion myLion;

        sphinx mySphinx(myMan, myLion);
        mySphinx.do_something_to_any_humanoid();
}

The problem is that when instantiating a sphinx I get the following errors:

./main.cpp: In function ‘int main()’:
./main.cpp:41:9: error: cannot declare variable ‘mySphinx’ to be of abstract type ‘sphinx’
  sphinx mySphinx(myMan, myLion);
         ^
./main.cpp:31:7: note:   because the following virtual functions are pure within ‘sphinx’:
 class sphinx : public humanoid, public cat {
       ^
./main.cpp:22:15: note:     virtual void cat::do_something_to_any_cat()
  virtual void do_something_to_any_cat(void) = 0;
               ^
./main.cpp:10:15: note:     virtual void humanoid::do_something_to_any_humanoid()
  virtual void do_something_to_any_humanoid(void) = 0;

I understand what the error says but can't figure out the proper way to do it. I do have a valid use for this and yes, 'sphinx' is both some sort of humanoid and some sort of feline and should inherit all their attributes and methods. Also, the 'man' and 'lion' need to be children so I can make an ugly sphinx. Can you picture a klingon and a persian?

Upvotes: 0

Views: 112

Answers (3)

Heath
Heath

Reputation: 21

@dfri : My intention is code like this:

#include <iostream>
using namespace std;

int main(void);

class Humanoid {
private:
        int HumanoidAttribute;

public:
        // pure virtual to ensure all derived classes define it
        virtual void doSomethingToAnyHumanoid(void) = 0;

        // pure virtual to ensure this is an abstract class
        // not really necessary because doSomethingToAnyHumanoid is pure virtual but good practice
        virtual ~Humanoid() = 0;
};
Humanoid::~Humanoid() { }

class Man: public Humanoid {
private:
        int ManAttribute;
public:
        void doSomethingToAnyHumanoid(void) override {
                cout << "Doing something to a Man which is a Humanoid.\n";
        }
};

class Cat {
private:
        int CatAttribute;
public:
        // pure virtual to ensure all derived classes define it
        virtual void doSomethingToAnyCat(void) = 0;

        // pure virtual to ensure this is an abstract class
        // not really necessary because doSomethingToAnyCat is pure virtual but good practice
        virtual ~Cat() = 0;
};
Cat::~Cat() { }

class Lion : public Cat {
private:
        int LionAttribute;
public:
        void doSomethingToAnyCat(void) override {
                cout << "Doing something to a Lion which is a Cat.\n";
        }
};

class Sphinx : public Humanoid, public Cat {
private:
        Humanoid & m_Humanoid;
        Cat & m_Cat;
        int SphinxAttribute;
public:
        Sphinx(Humanoid & HumanoidIn, Cat & CatIn) : m_Humanoid(HumanoidIn), m_Cat(CatIn) { }
        void doSomethingToAnyHumanoid(void) { m_Humanoid.doSomethingToAnyHumanoid(); }
        void doSomethingToAnyCat(void) { m_Cat.doSomethingToAnyCat(); }
};

int main(void) {
        Man myMan;
        Lion myLion;

        Sphinx mySphinx(myMan, myLion);
        mySphinx.doSomethingToAnyHumanoid();
}

Since the methods of the base classes are pure virtual I should always notice when compiling the Sphinx class if I'm missing some wrapper methods.

Anyone have any other comments? I'm new to C++ so any tips are welcome!

Upvotes: 0

dfrib
dfrib

Reputation: 73186

First of all, we should look at what you're attempting to achieve: abstract base classes working as interfaces to blueprint methods that objects of non-abstract leaf classes can dispatch.

For this purpose, lets leave the class variables out of it, and focus on an example with only class methods:

#include <iostream>

/* astract base classes */
/* -------------------- */
class Humanoid {
public:
  virtual ~Humanoid() = 0;
    // Pure virtual; ensures Humanoid is abstract, even in case we supply
    // default implementations for all "blueprinted" method (not the case
    // for the Humanoid type, but for the Cat type)

  // no default implementation supplied: derived classes
  // must implement this method in order to be non-abstract
  virtual void doSomethingToAnyHumanoid() = 0;
};
Humanoid::~Humanoid() {}

class Cat {
public:
  virtual ~Cat() = 0; // Pure virtual; ensures Cat is abstract ...

  // supply a default implementation
  virtual void doSomethingToAnyCat() {
    std::cout << "Cat default implementation\n";
  }
};
Cat::~Cat() {}

These two base classes are abstract as they have at least one pure virtual method (in the case of Cat, only its DTOR).

As a first attempt, we'll try to implement a sphinx class that is common leaf class of Humanoid as well as Cat:

/* non-abstract leaf classes */
/* ------------------------- */
class NotReallyASphinx : public Humanoid, public Cat {
public:
  // by choice (not necessity): use default cat implementation

  // for non-abstractness: implement custom Humanoid implementation
  virtual void doSomethingToAnyHumanoid() override {
    std::cout << "Needs to be done on four legs ...\n";
  }
};

/* example usage */
int main() {
  NotReallyASphinx notReallyASphinx;
  notReallyASphinx.doSomethingToAnyHumanoid(); // Needs to be done on four legs ...
  notReallyASphinx.doSomethingToAnyCat();      // Cat default implementation

  return 0;
}

Since Humanoid provides no default implementation of doSomethingToAnyHumanoid, its a necessity that we implement it in NotReallyASphinx, in order for the latter to be non-abstract.


Now, for your slightly more sphinx-like attempt at a sphinx, it seems you want to make use of a Lion and a Man instance (wrapped, however, in addresses of their abstract base classes) to construct the basis for an UglySphinx. Lets start by implementing the minimal non-abstract types Lion and Man:

/* non-abstract leaf classes */
/* ------------------------- */
class Man: public Humanoid {
public:
  virtual void doSomethingToAnyHumanoid() override {
    std::cout << "Doing something to a man which is a humanoid.\n";
  }
};

class Lion : public Cat {
public:
  virtual void doSomethingToAnyCat() override {
    std::cout << "Doing something to a lion which is a cat.\n";
  }
};

If you, however, want to construct an UglySphinx based on instances of the two types above, you need to resort to type composition: where and UglySphinx instance in itself owns (responsibility of) instances of Lion and Man.

/* non-abstract leaf classes */
/* ------------------------- */
class UglySphinx : public Humanoid, public Cat {
private:
  Humanoid& m_humanoid;
  Cat& m_cat;
    // we know that humanoidIn and catIn will persist at least
    // as long as the UglySphinx instance, so we'll just work
    // on the stack for this example. An alternative is using
    // pointers here and working on heap; which includes responsibility
    // handling of the heap objects (e.g. owned by UglySphinx)

public:
  UglySphinx(Humanoid& humanoidIn, Cat& catIn) : m_humanoid(humanoidIn), m_cat(catIn) { }

  // by choice (not necessity): use dynamic dispatch to use the
  // m_cat(lion) implementation
  virtual void doSomethingToAnyCat() override {
    m_cat.doSomethingToAnyCat();
  }
  // for non-abstractness: we must implement custom Humanoid implementation,
  // but as above, use m_humanoid(man) for this purpose
  virtual void doSomethingToAnyHumanoid() override {
    m_humanoid.doSomethingToAnyHumanoid();
  }
};

Which would allow us to construct an UglySphinx using a Man and a Lion object:

/* example usage */
int main() {
  Man aMan;
  Lion aLion;
  UglySphinx uglySphinx(aMan, aLion);

  uglySphinx.doSomethingToAnyHumanoid(); // Doing something to a man which is a humanoid.
  uglySphinx.doSomethingToAnyCat();      // Doing something to a lion which is a cat.

  return 0;
}

Since you're intent on working with addresses (alternative pointers) of the abstract base class types, the UglySphinx type above would allow us to create an (really) UglySphinx instance based on e.g. a Man instance and say, a RagDoll:

/* non-abstract leaf classes */
/* ------------------------- */
class RagDoll : public Cat {
public:
  virtual void doSomethingToAnyCat() override {
    std::cout << "Doing something to a ragdoll which is a cat.\n";
  }
};

/* example usage */
int main() {
  Man aMan;
  RagDoll aRagdoll;
  UglySphinx reallyUglySphinx(aMan, aRagdoll);

  reallyUglySphinx.doSomethingToAnyHumanoid(); // Doing something to a man which is a humanoid.
  reallyUglySphinx.doSomethingToAnyCat();      // Doing something to a ragdoll which is a cat.

  return 0;
}

I can't see the risk of creating ragdoll-based Sphinx being intentional, so perhaps what you really wanted to do was create a PersistantUglySphinx which is guaranteed to be composed of Man and Lion instances (w.r.t. the methods doSomethingToAnyHumanoid() and doSomethingToAnyCat(), respectively)?

/* non-abstract leaf classes */
/* ------------------------- */
class PersistantUglySphinx : public Man, public Lion {
private:
  Man m_man;
  Lion m_lion;

public:
  PersistantUglySphinx(Man& manIn, Lion& lionIn) : m_man(manIn), m_lion(lionIn) { }

  virtual void doSomethingToAnyCat() override {
    m_lion.doSomethingToAnyCat();
  }

  virtual void doSomethingToAnyHumanoid() override {
    m_man.doSomethingToAnyHumanoid();
  }
};

Just beware that with this approach we've run into an possibly best-practices issue. Since PersistantUglySphinx derives from Man and Lion, the two latter types are no longer leaf classes, but nor have they been made abstract. Especially when working with "interface"-like inheritance as in this example, I at least personally try to adhere to Scott Meyers advice that we should "Make non-leaf classes abstract" (Item 33 from "More Effective C++").


To wrap it up: make sure to understand what an abstract class is, and what is required of a class derived from an abstract class to be non-abstract (i.e., implementations of all pure virtual methods of the abstract base class).

Upvotes: 0

Jack
Jack

Reputation: 133587

Inheritance expresses the is-a relationship.

If a sphinx is both a humanoid and a cat then the type must be able to be used in all contexts in which a humanoid or a cat is required. Since these two types have two pure virtual methods then you must provide implementations for such methods.

Upvotes: 2

Related Questions