Reputation: 21
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
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
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
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