I was doing an experiment on inheritance and abstract classes without using pointers (avoiding the use of new when possible), and I came across a behavior that makes sense but I havent found documentation on it on the internet (neither a name for it or example).
I create a class, Abstract
that has two methods, one is defined and the other is not. Then, a class Inherited
inherits from Abstract
and implements this second method.
Heres the classes:
Abstract.hpp:
#ifndef ABSTRACT_HPP
# define ABSTRACT_HPP
#include <iostream>
class Abstract {
public:
Abstract() {};
~Abstract() {};
virtual void DoSomethingAbstract() = 0;
void DoSomethingNormal() {
std::cout << "Inside Abstract::DoSomethingNormal" << std::endl;
DoSomethingAbstract();
}
};
#endif /* ABSTRACT_HPP */
Inherited.hpp :
#ifndef Inherited_HPP
# define Inherited_HPP
#include "Abstract.hpp"
class Inherited : public Abstract {
public:
Inherited() {};
~Inherited() {};
virtual void DoSomethingAbstract() {
std::cout << "Inside Inherited::DoSomethingAbstract" << std::endl;
}
};
#endif /* Inherited_HPP */
The following main works as expected (the only implementation of each function is the one that is called):
#include "Abstract.hpp"
#include "Inherited.hpp"
int main() {
Inherited a;
a.DoSomethingNormal();
return 0;
}
output:
Inside Abstract::DoSomethingNormal
Inside Inherited::DoSomethingAbstract
Mostly I'd like to know if this is a UB and I'm just getting lucky, an unfrequent way of checking runtime polymorphism (which should not since there are no pointers to abstract classes here), or a perfectly defined behaviour somewhere in the standard.
PS: I am compiling with g 9.4.0, with flags `-std=c 98 -Wno-c 0x-compat.
CodePudding user response:
Your example seems simple -- and for the most part, it is. But it leads to some important concepts. First, what you can do is a google on c polymorphism
. There are a lot of hits, and the first several didn't seem to be wrong.
Let's look at some of your code:
virtual void DoSomethingAbstract() = 0;
This is referred to as a pure virtual function. It means you won't actually make this method, but you're going to reserve a slot for it (and can call it). It means you can not make instances of this class -- it's abstract. If you try to make one, the compiler will complain. Your subclasses MUST provide implementations or they are also abstract and can't be instantiated.
void DoSomethingNormal() { std::cout << "Inside Abstract::DoSomethingNormal" << std::endl; DoSomethingAbstract(); }
This method is NOT virtual. Your subclass can make its own copy of this method, but then you can get weird results. Let's say you do this: class Inherited: public Abstract { public: void DoSomethingNormal() {...} };
void blah(Abstract &obj) {
obj.DoSomethingNormal();
}
It won't matter whether you pass in an Abstract or an Inherited -- because you didn't declare the method virtual, and because blah()
doesn't know what you're passing in but thinks it's an Abstract, you'll get Abstract's version of DoSomethingNormal()
.
But if you declare the method virtual, you're saying, "Use whichever version corresponds to the class that actually was instantiated."
This is polymorphism at it's most useful, important role.