C++ inheritance and/or Polymorphism Implementation and explanation

Hello everyone,

I am fairly new to C++, and I am trying to clearly understand Inheritance and polymorphism, having said that, I will very much appreciatte a clear example and/or implementation of it.

I am currently working on a project in Which I have two classes, and they basically parse a packet of data from a source, however the packet changes depending on the source, therefore I have written two different classes, each for its corresponding source, these two classes use pretty much the same functions, but with some small differences, I think that there should be a nice and well structured way of doing this in one class using inheritance, I am simply not sure about how to implement it.
Thank you. :slight_smile:

class PARSER1 {
  
  public:
  
    PARSER1();
    void commonFunction1();  
    bool commonFunction2(char rxd);
    uint8_t commonFunction3();
    void commonFunction4(char buf[]);
    
}
class PARSER2 {
  
  public:
  
    PARSER2();
    void commonFunction2_1();  
    bool commonFunction2_2(char rxd);
    uint8_t commonFunction3_3();
    void commonFunction4_4(char buf[]);
    
}
/*In the main program I create an instance of the class that I need depending on the source Ideally I could do this in the Base class */

PARSER2 parser2


PARSER1 parser1

Your question is too broad and I think you don't yet know what you don't know. I recommend finding an online tutorial on the subject.

I can also recomend this Book. It's great for the first move from C to C++. But, it assumes that you know C concepts right off the bat. There's no hand holding when it comes to pointers, structs, data types, arrays, control structures, etc.

I don't see inheritance nor polymorphism in what you posted.. indeed worth reading something about the underlying concepts and then come back to your special case

// Simple demonstration of inheritance.
#include <iostream> 

class Base { // base class
public:
  int b;

  void getdata() {
    std::cout << " Enter the value of b = ";
    std::cin >> b;
  }

  virtual void times2() const { std::cout << b * b << std::endl; }

};

class Derive : public Base { // derived class
private:
  int d;

public:
  void readdata() {
    std::cout << " Enter the value of d = ";
    std::cin >> d;
  }

  void product() { std::cout << "Product = " << b * d << std::endl; }

  // overridden method.
  void times2() const override { std::cout << d * d << std::endl; }
};

int main() {
  Derive d;
  d.getdata();
  d.readdata();
  d.product();
  d.times2();
}

Output:
Enter the value of b = 2
Enter the value of d = 3
Product = 6
9

JimEli:

// Simple demonstration of inheritance.

#include

class Base { // base class
public:
 int b;

void getdata() {
   std::cout << " Enter the value of b = ";
   std::cin >> b;
 }

virtual void times2() const { std::cout << b * b << std::endl; }

};

class Derive : public Base { // derived class
private:
 int d;

public:
 void readdata() {
   std::cout << " Enter the value of d = ";
   std::cin >> d;
 }

void product() { std::cout << "Product = " << b * d << std::endl; }

// overridden method.
 void times2() const override { std::cout << d * d << std::endl; }
};

int main() {
 Derive d;
 d.getdata();
 d.readdata();
 d.product();
 d.times2();
}




**Output:**
Enter the value of b = 2
Enter the value of d = 3
Product = 6
9

It would have been more helpful if you had presented that in the context of an Arduino sketch rather than straight-up C++. This is, after all, the Arduino forum.

gfvalvo:
It would have been more helpful if you had presented that in the context of an Arduino sketch rather than straight-up C++. This is, after all, the Arduino forum.

The Arduino context was purposely avoided to highlight the underlying elements and structure of C++ polymorphism and inheritance. The initial response linked to the purchase of a dated (1995) non-Arduino C++ book, and the previous post recommended, "reading something about the underlying concepts". All of which makes my simple example appropriate to the thread. If the leap to Arduino specifics is too great, I welcome you to post a more helpful example.

gfvalvo:
It would have been more helpful if you had presented that in the context of an Arduino sketch rather than straight-up C++. This is, after all, the Arduino forum.

That was exactly what I was looking for, an example of a base and derived classes, since in my special case I just need a base class and the posibility of adding more derived classes, having the base class as an interface to access special methods, I will have to read some on the virtual keyword.

Thanks

class Parser
{
  public:
    Parser() {};
    virtual void commonFunction1() = 0;  // Pure Virtual: MUST be declared in derived class
    virtual bool commonFunction2(char rxd) {return true;};  //  Virtual: May be overridden in derived class
    uint8_t commonFunction3() {return 0;}
    void commonFunction4(char buf[]) {}
};


class Parser1 : public Parser
{
  public:
    Parser1() {};
    void commonFunction1() {};
    bool commonFunction2(char rxd) {return false;}; // Override of the base class
};


class Parser2 : public Parser
{
  public:
    Parser2() {};
    void commonFunction1() {};
    // bool commonFunction2(char rxd)  <- Inherits the base class behavior
};


// Create an instance of each of the two derived types
Parser1 parser1;
Parser2 parser2;


// Pass an object of any derived type
void parserInit(Parser &parser)
{
  parser.commonFunction1();  // This calls commonFunction1() for the correct derived type
}


void setup() {
  parserInit(parser1);
  parserInit(parser2);
  }
  
void loop() {}
class Parser1 : public Parser {
  public:
    Parser1() {}
    void commonFunction1() override {}
    bool commonFunction2(char rxd) override { return false; } // Override of the base class
};

It's recommended to add "override" to the methods that override base class methods, so the compiler can warn you if you make a typo, or if you mess up the parameter types.

Pieter

johnwasser:

class Parser

{
 public:
   Parser() {};
   virtual void commonFunction1() = 0;  // Pure Virtual: MUST be declared in derived class
   virtual bool commonFunction2(char rxd) {return true;};  //  Virtual: May be overridden in derived class
   uint8_t commonFunction3() {return 0;}
   void commonFunction4(char buf) {}
};

class Parser1 : public Parser
{
 public:
   Parser1() {};
   void commonFunction1() {};
   bool commonFunction2(char rxd) {return false;}; // Override of the base class
};

class Parser2 : public Parser
{
 public:
   Parser2() {};
   void commonFunction1() {};
   // bool commonFunction2(char rxd) ← Inherits the base class behavior
};

// Create an instance of each of the two derived types
Parser1 parser1;
Parser2 parser2;

// Pass an object of any derived type
void parserInit(Parser &parser)
{
 parser.commonFunction1();  // This calls commonFunction1() for the correct derived type
}

void setup() {
 parserInit(parser1);
 parserInit(parser2);
 }
 
void loop() {}

johnwasser:

class Parser

{
 public:
   Parser() {};
   virtual void commonFunction1() = 0;  // Pure Virtual: MUST be declared in derived class
   virtual bool commonFunction2(char rxd) {return true;};  //  Virtual: May be overridden in derived class
   uint8_t commonFunction3() {return 0;}
   void commonFunction4(char buf) {}
};

class Parser1 : public Parser
{
 public:
   Parser1() {};
   void commonFunction1() {};
   bool commonFunction2(char rxd) {return false;}; // Override of the base class
};

class Parser2 : public Parser
{
 public:
   Parser2() {};
   void commonFunction1() {};
   // bool commonFunction2(char rxd) ← Inherits the base class behavior
};

// Create an instance of each of the two derived types
Parser1 parser1;
Parser2 parser2;

// Pass an object of any derived type
void parserInit(Parser &parser)
{
 parser.commonFunction1();  // This calls commonFunction1() for the correct derived type
}

void setup() {
 parserInit(parser1);
 parserInit(parser2);
 }
 
void loop() {}

Thank you very much this is a perfect example as well, however I will choose what parser class to use on Run time in the setup function, therefore I will only use one class at the time, since I am running this in a micro I am bit concerned about memory occupation by the unused instance, is there a way to create the specific instance in the setup function ? based on a decisition, or does it not really affect efficiency ? does the unused obeject still get some memory allocated to it ?

Thanks !

The compiler will keep in program memory all the code for the « functions » that are being used and per each instance of course all the variables that are required to describe all the instances you create and use (in SRAM) and if you use virtual stuff there will be a small indirection array of pointers used at run time to decide which function to call based on real object class

Hence having classes is not so much of a penalty

here is yet another example with inheritance and polymorphism

You have a class Person, which has 2 subclass (VIP or Arduinist).

A Person is defined by its name and sex
A VIP is a Person who has done some noticeable major work on a certain year
an Arduinist is also a Person but only defined by the year when joining the forum

every class has a information() method that is defined as virtual in the base class and is overridden in the subclasses

I then declare a few VIPs and a few Arduinists and group them in an array of Persons

Then in the setup, I go through the array and ask information about everyone. Technically the type of each element in the array is Person, but the compiler is smart enough to recognize they are of different subclasses and when calling the information() method, it picks the right one based on the actual class of that element, as you can see in the Serial Console (opened at 115200 bauds)

const uint8_t maxStringSize = 30;

class Person {
  protected:
    bool isMale = true;
    char personName[maxStringSize + 1]; // +1 for trailing null char

  public:
    Person(const bool m, const char* n): isMale(m)
    {
      if (n) strncpy(personName, n, maxStringSize);
      else strncpy(personName, "no name", maxStringSize);
      personName[maxStringSize] = '\0'; // just in case
    }

    virtual float information()   // without virtual you don't get polymorphisme -> early binding. otherwise late binding
    {
      Serial.print(F("@Person\tname: "));
      Serial.print(personName);
      Serial.print(F("\tsex: "));
      Serial.print(isMale ? F("male") : F("female"));
      return 0;
    }
};


class VIP: public Person {
  protected:
    uint16_t keyYear;
    char famousFor[maxStringSize + 1]; // +1 for trailing null char

  public:
    VIP(const bool m, const char* n, uint16_t y, const char* majorTopic): Person(m, n), keyYear(y) {
      if (majorTopic) {
        strncpy(famousFor, majorTopic, maxStringSize);
        famousFor[maxStringSize] = '\0'; // just in case
      }
    }

    virtual float information() override  // without virtual you don't get polymorphisme -> early binding. otherwise late binding
    {
      Person::information();
      Serial.print(F("\n\t@VIP:\tFamous for: "));
      Serial.print(famousFor);
      Serial.print(F("\tdone from year: "));
      Serial.print(keyYear);
      return 0;
    }
};

class Arduinist: public Person {
  protected:
    uint16_t joinedForumInYear;

  public:
    Arduinist(const bool m, const char* n, uint16_t y): Person(m, n), joinedForumInYear(y) {}

    virtual float information()  override // without virtual you don't get polymorphisme -> early binding. otherwise late binding
    {
      Person::information();
      Serial.print(F("\n\t@Arduinist:\tJoined forum in: "));
      Serial.print(joinedForumInYear);
      return 0;
    }
};

// ----------------------

VIP lovelace(false, "Ada Byron (Lovelace)", 1842, "abstract science of operations");
VIP ritchie(true, "Dennis Ritchie", 1972, "the C language");
VIP stroustrup(true, "Bjarne Stroustrup", 1983, "the C++ language");

Arduinist JimEli(true, "Jim Eli", 2009);
Arduinist gfvalvo(true, "GF Valvo", 2016);
Arduinist buggy_Code(true, "Buggy Code", 2019);

Person* team[] = {&lovelace, &ritchie, &stroustrup, &JimEli, &gfvalvo, &buggy_Code};
uint8_t teamSize = sizeof(team) / sizeof(team[0]);

void setup()
{
  Serial.begin(115200);
  for (byte i = 0; i < teamSize; i++)  {
    team[i]->information();
    Serial.println('\n');
  }
}

void loop() {}

You should see in the console:

[color=purple]
@Person	name: Ada Byron (Lovelace)	sex: female
	@VIP:	Famous for: abstract science of operations	done from year: 1842

@Person	name: Dennis Ritchie	sex: male
	@VIP:	Famous for: the C language	done from year: 1972

@Person	name: Bjarne Stroustrup	sex: male
	@VIP:	Famous for: the C++ language	done from year: 1983

@Person	name: Jim Eli	sex: male
	@Arduinist:	Joined forum in: 2009

@Person	name: GF Valvo	sex: male
	@Arduinist:	Joined forum in: 2016

@Person	name: Buggy Code	sex: male
	@Arduinist:	Joined forum in: 2019

[/color]

PS/ this code should not define char arrays with massive size if you use only a subset of the memory, it should know ideally how to handle a pointer to flash memory and you could use the PROGMEM trick to minimize impact on SRAM. this would have added un-necessary complexity to the topic of this discussion

Buggy_Code:
Thank you very much this is a perfect example as well, however I will choose what parser class to use on Run time in the setup function, therefore I will only use one class at the time, since I am running this in a micro I am bit concerned about memory occupation by the unused instance, is there a way to create the specific instance in the setup function ? based on a decisition, or does it not really affect efficiency ? does the unused obeject still get some memory allocated to it ?

Thanks !

FWIW, if you declare/define a class(es) and, or derived class(es), but never instantiate them, there is no memory used by the un-instantiated class(es). In most cases, even if you instantiate the class, but then never use it, the compiler is smart enough to catch this and use no additional memory.

J-M-L:
here is yet another example with inheritance and polymorphism

You have a class Person, which has 2 subclass (VIP or Arduinist).

A Person is defined by its name and sex
A VIP is a Person who has done some noticeable major work on a certain year
an Arduinist is also a Person but only defined by the year when joining the forum

every class has a information() method that is defined as virtual in the base class and is overridden in the subclasses

I then declare a few VIPs and a few Arduinists and group them in an array of Persons

Then in the setup, I go through the array and ask information about everyone. Technically the type of each element in the array is Person, but the compiler is smart enough to recognize they are of different subclasses and when calling the information() method, it picks the right one based on the actual class of that element, as you can see in the Serial Console (opened at 115200 bauds)

const uint8_t maxStringSize = 30;

class Person {
  protected:
    bool isMale = true;
    char personName[maxStringSize + 1]; // +1 for trailing null char

public:
    Person(const bool m, const char* n): isMale(m)
    {
      if (n) strncpy(personName, n, maxStringSize);
      else strncpy(personName, “no name”, maxStringSize);
      personName[maxStringSize] = ‘\0’; // just in case
    }

virtual float information()  // without virtual you don’t get polymorphisme → early binding. otherwise late binding
    {
      Serial.print(F("@Person\tname: “));
      Serial.print(personName);
      Serial.print(F(”\tsex: "));
      Serial.print(isMale ? F(“male”) : F(“female”));
      return 0;
    }
};

class VIP: public Person {
  protected:
    uint16_t keyYear;
    char famousFor[maxStringSize + 1]; // +1 for trailing null char

public:
    VIP(const bool m, const char* n, uint16_t y, const char* majorTopic): Person(m, n), keyYear(y) {
      if (majorTopic) {
        strncpy(famousFor, majorTopic, maxStringSize);
        famousFor[maxStringSize] = ‘\0’; // just in case
      }
    }

virtual float information() override  // without virtual you don’t get polymorphisme → early binding. otherwise late binding
    {
      Person::information();
      Serial.print(F("\n\t@VIP:\tFamous for: “));
      Serial.print(famousFor);
      Serial.print(F(”\tdone from year: "));
      Serial.print(keyYear);
      return 0;
    }
};

class Arduinist: public Person {
  protected:
    uint16_t joinedForumInYear;

public:
    Arduinist(const bool m, const char* n, uint16_t y): Person(m, n), joinedForumInYear(y) {}

virtual float information()  override // without virtual you don’t get polymorphisme → early binding. otherwise late binding
    {
      Person::information();
      Serial.print(F("\n\t@Arduinist:\tJoined forum in: "));
      Serial.print(joinedForumInYear);
      return 0;
    }
};

// ----------------------

VIP lovelace(false, “Ada Byron (Lovelace)”, 1842, “abstract science of operations”);
VIP ritchie(true, “Dennis Ritchie”, 1972, “the C language”);
VIP stroustrup(true, “Bjarne Stroustrup”, 1983, “the C++ language”);

Arduinist JimEli(true, “Jim Eli”, 2009);
Arduinist gfvalvo(true, “GF Valvo”, 2016);
Arduinist buggy_Code(true, “Buggy Code”, 2019);

Person* team = {&lovelace, &ritchie, &stroustrup, &JimEli, &gfvalvo, &buggy_Code};
uint8_t teamSize = sizeof(team) / sizeof(team[0]);

void setup()
{
  Serial.begin(115200);
  for (byte i = 0; i < teamSize; i++)  {
    team[i]->information();
    Serial.println(’\n’);
  }
}

void loop() {}




You should see in the console:


[color=purple]
@Person name: Ada Byron (Lovelace) sex: female
@VIP: Famous for: abstract science of operations done from year: 1842

@Person name: Dennis Ritchie sex: male
@VIP: Famous for: the C language done from year: 1972

@Person name: Bjarne Stroustrup sex: male
@VIP: Famous for: the C++ language done from year: 1983

@Person name: Jim Eli sex: male
@Arduinist: Joined forum in: 2009

@Person name: GF Valvo sex: male
@Arduinist: Joined forum in: 2016

@Person name: Buggy Code sex: male
@Arduinist: Joined forum in: 2019

[/color]





PS/ this code should not define char arrays with massive size if you use only a subset of the memory, it should know ideally how to handle a pointer to flash memory and you could use the PROGMEM trick to minimize impact on SRAM. this would have added un-necessary complexity to the topic of this discussion

Thank you very much for such a nice example an explanation.

I hope the questions below make some sense, I am just trying to understand every detail as clear as possible.

if there were at least one pure virtual methid in the Person Class will this be an abstract Class ?

Person* team[] = {&lovelace, &ritchie, &stroustrup, &JimEli, &gfvalvo, &buggy_Code};

/*
*
does this line declare an array of pointers of type Person referenced to the respective indexed object?
if so, since this array declaration decay to a pointer (not sure about this) will this be a pointer to a pointer?
* 
*/

This line Person* team[] = {&lovelace, &ritchie, &stroustrup, &JimEli, &gfvalvo, &buggy_Code};does indeed declare and define an array of pointers to instances that are a kind of Person.

When you refer to a derived class instance using a pointer or a reference to the base Person class, as demonstrated in the code above, you can call a virtual function for that instance (object) and execute the derived class’s version of the function.

Virtual functions ensure that the correct member function is called for an instance, regardless of the type of reference (or pointer) used for function call, achieving Runtime polymorphism.

So The array does hold the pointer to the instances and The compiler - noticing the call to a virtual member function - will generate code for resolving which function to call at Run-time. So That’s where instead of having a direct jump to the function code statically resolved, some cycles will be spent in a look up table to find where to jump.

Makes sense?

Does inheritance exist in the example of the following struct?

struct product
{
 int weight;
 float price;
};
product apple;
product banana, melon;

GolamMostafa:
Does inheritance exist in the example of the following struct?

struct product

{
int weight;
float price;
};
product apple;
product banana, melon;

That simple creates 3 independent instances of the struct. Same as:

int apple;
int banana, melon;

GolamMostafa:
Does inheritance exist in the example of the following struct?

struct product

{
int weight;
float price;
};
product apple;
product banana, melon;

i don’t see any hierarchy - so not sure what you mean

Buggy_Code:
Thank you very much for such a nice example an explanation.

I hope the questions below make some sense, I am just trying to understand every detail as clear as possible.

if there were at least one pure virtual methid in the Person Class will this be an abstract Class ?

Person* team[] = {&lovelace, &ritchie, &stroustrup, &JimEli, &gfvalvo, &buggy_Code};

/*
*
does this line declare an array of pointers of type Person referenced to the respective indexed object?
if so, since this array declaration decay to a pointer (not sure about this) will this be a pointer to a pointer?
*
*/

Yes it makes perfect sense, thank you.

Great! glad it was useful