-2

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.

carce-bo
  • 480
  • 2
  • 10
  • That's all fine, because `a` is an `Inherited` object. It should also work fine if you had e.g. `Abstract* b = new Inherited; b->DoSomethingNormal();` – Some programmer dude Aug 01 '22 at 12:33
  • whats wrong with the output? What else did you expect? Why do you think this would be undefined? – 463035818_is_not_an_ai Aug 01 '22 at 12:33
  • Thats what I wanted to avoid. Seems like theres nowhere on the internet that explains polymorphism without the usage of new, which made me assume (I started with C++ a year ago) theres no other way of going around it – carce-bo Aug 01 '22 at 12:33
  • 2
    That's a well known design pattern called _Template Method_ – πάντα ῥεῖ Aug 01 '22 at 12:34
  • 1
    you do not need new. `void foo(Abstract& a) { ...do something... }` then in `main` you create a `Inherited` object and pass it to `foo`. Tada: polymorphism without `new` – 463035818_is_not_an_ai Aug 01 '22 at 12:35
  • you do not need dynamic allocation to get a pointer or reference to an object – 463035818_is_not_an_ai Aug 01 '22 at 12:35
  • I didnt say I need new. I said I had not found a piece of code that explained polymorphism without it. – carce-bo Aug 01 '22 at 12:37
  • What about the sample programs in your C++ textbook, that you're using to learn C++? Any informative examples there? – Sam Varshavchik Aug 01 '22 at 12:37
  • @carce-bo Sure there is. For example https://www.learncpp.com/cpp-tutorial/virtual-functions/ starts with an example using just a reference to an automatic variable. In fact, `new` is not really relevant to polymorphism at all. How the objects were created is irrelevant to the behavior of virtual functions. You should almost never use `new` directly anyway in modern C++. You need to learn the language from structured material, not by web searches. Either a [good textbook](https://stackoverflow.com/questions/388242) or at least some reputable site. (The one I linked is decent in my opinion.) – user17732522 Aug 01 '22 at 12:37
  • I guess the very first line of the link you gave already answers my question: `A virtual function is a special type of function that, when called, resolves to the most-derived version of the function that exists between the base and derived class.` – carce-bo Aug 01 '22 at 12:42
  • Yes. Something like this should always be the explanation when virtual functions are introduced. That's what they do, nothing more or less. (My only complaint is that they should have said which derived class they mean. They mean the most-derived class of which the referenced object is a base subobject.) The issue here is that most of the top links you get when doing web searches about how to do something or how something works in C++ are garbage. You need to ignore them and stick to only known reputable sites. It can be hard to figure out which these are though as a beginner. – user17732522 Aug 01 '22 at 12:47
  • @user17732522 • what are the **good** sites? LearnCpp. Good sites, but not for learning C++ from scratch: ISO CPP, CppReference. The bad sites for learning C++ (some are bad tutorial sites, some are bad reference sites, some are bad competitive coding / puzzle sites misused *as a learning site*): Binary Search, Chegg, CodeWars, Codeforces, Cplusplus, Dcoder, Edabit, GeeksforGeeks, HackerRank, Kattis, Learn Programming Languages (C and C++), Leetcode,Programiz, Project Euler, TutorialsPoint, W3Schools. (I like Project Euler, as puzzle site. Not as a learning site.) – Eljay Aug 01 '22 at 12:52
  • Well I'll always be able to post my question on stackoverflow and exchange some reliable links for some downvotes :P – carce-bo Aug 01 '22 at 12:52
  • Polymorphism doesn't really make much sense without the use of pointers or references. – Some programmer dude Aug 01 '22 at 12:59
  • 3
    So what we have learned today is that there are sites on the internet that contain incorrect information. We must immediately fix that! https://xkcd.com/386/ – BoP Aug 01 '22 at 13:22

1 Answers1

1

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.

Joseph Larson
  • 8,530
  • 1
  • 19
  • 36