Array of multiple child/derived class objects in Arduino?

Hi everyone!

I'm trying to understand the best way to create an array that holds various children classes that are all derived from the same parent class, with an emphasis on making sure I don't have any memory leaks. I'm not even sure what a memory leak would do in Arduino...wouldn't it resolve itself upon powering down and powering back up?

Any help or advice would be greatly appreciated!


After researching as best as I can, the two methods I have seen are:

  • (Option 1) Creating a pointer array of the parent class, where each element points to a child object.
    But where am I supposed to manually delete these to avoid memory leaks? I can't delete them in loop() because I'm always using them in loop()
Shape *shapesArray[4];
shapesArray[0] = new Circle(5);
shapesArray[1] = new Circle(10);
shapesArray[2] = new Triangle(10,5);
shapesArray[3] = new Rectangle(10,5);

for(int i = 0; i <4; i++)
{
shapesArray[i]->displayArea();
}
  • (Option 2) Create a vector of smart pointers
    Is this better? Will it automatically delete the objects and avoid memory leaks? Is this even possible in arduino? The arduino IDE doesn't seem to know what std::vector is
std::vector< std::shared_ptr<Shape> > shapesArray;
shapesArray.push_back(std::shared_ptr<Circle>(new Circle(5)));
shapesArray.push_back(std::shared_ptr<Circle>(new Circle(10)));
shapesArray.push_back(std::shared_ptr<Triangle>(new Triangle(10, 5)));
shapesArray.push_back(std::shared_ptr<Rectangle>(new Rectangle(10, 5)));

for(i = 0; i <4; i++)
{
shapesArray[i]->displayArea();
}

Full Code Example Using Option 1
This works but I'm not sure if this is safe and okay to do.

// Class definitions: a Shape (base class) has three child classes: circle, triangle, and rectangle. While they all have the variable "area", each child calculates their area differently

class Shape
{

public:
virtual void displayArea()
{
  //this function will behave diffrently for each child class
  Serial.println("If you're reading this it didn't work");
}

virtual ~Shape()
{
  //apparently the base class' destructor should always be virtual when using derived classes and polymorphism?
}
protected:
float area;

};

class Circle : public Shape
{
  public:
  Circle(float radius)
  {
    area = radius * radius * 3.14;
  }

  void displayArea()
  {
    Serial.print("This circle's area is: ");
    Serial.println(area);    
  }
  
};

class Triangle : public Shape
{
  public:
  Triangle(float height, float base)
  {
    area = 0.5*height*base;
  }

  void displayArea()
  {
    Serial.print("This triangle's area is: ");
    Serial.println(area);    
  }
  
};

class Rectangle : public Shape
{
  public:
  Rectangle(float height, float length)
  {
    area = height*length;
  }

  void displayArea()
  {
    Serial.print("This rectangle's area is: ");
    Serial.println(area);    
  }
  
};



const int numShapes = 4;
Shape *shapesArray[numShapes];

void setup() {
  Serial.begin(9600);


shapesArray[0] = new Circle(5);
shapesArray[1] = new Circle(10);
shapesArray[2] = new Triangle(10,5);
shapesArray[3] = new Rectangle(10,5);
/*
* But now I'm supposed to delete these manually right?
* I need these objects for the entirety of my program,
* and loop() is going to keep going until I turn the Arduino off.
* So when/where am I supposed to put the code to delete these
* from memory to avoid memory leaks?
*/

 
}

void loop() {

 Serial.println("Display area for which shape in the array?");
 
 while(Serial.available() == 0)
 {
  //wait for input  
 }
 
  int value = Serial.parseInt();
  if(value >= 0 && value <= numShapes - 1)
  {
    shapesArray[value]->displayArea();
  }
  else
  {
    Serial.print("Invalid number entered. Please enter a value between 0 and ");
    Serial.println(numShapes - 1);
  }
 


} // end of loop

Sources:

I would use base pointers combined with virtual destructors.

1 Like

Hello

Why do you want to delete the objects if you need them later ?

Memory leaks occurs if your object doesn't properly delete memory that it allocated dynamically. Your objects does not allocate any dynamic memory so you don't need to worry about memory leaks.

1 Like

Thank you both!

Re: @guix 's question:

Why do you want to delete the objects if you need them later ?

I might not fully understand what a memory leak is. My understanding is that because I am using the "new" command, e.g. shapesArray[0] = new Circle(5); that it is creating dynamically allocated memory. And unless at some point in my program I delete this object/free up this memory, it will cause some sort of problems in the future. (I'm not sure if these problems would be specific to my program while the arduino is on, or a physical problem with the actual memory on the arduino hardware)

Re: @Whandall

I would use base pointers combined with virtual destructors.

Is what I did in the Example Code at the end of my original post along the lines of what you are referring to? I created a "virtual destructor" for the base object. Do I need to explicitly create the destructor for each child object? And if so, do I need to put anything inside it?

Overall, my understanding was that if you don't have a matching delete operation for every new operation you perform, that it will result in a memory leak.

Yes I think you have a misconception of what is a memory leak

Your program create your objects with new, that is dynamic memory allocation. IF you had to destroy these objects (you don't) then you would have to delete them. There would be no memory leaks, unless your object themselves dynamically allocated some memory and did not properly delete it when it had to (in the destructor, for example). But that is not the case, in your classes you never use new or malloc, so you have nothing to worry about :slight_smile:

When the arduino is restarted, the memory is wiped and clean as new, your objects are created as if they never existed before :slight_smile:

1 Like

ohh!!!! It makes sense now! Woohoo thank you!

So this means for now, I don't need to do anything different, and can continue with my project using "Option 1."

But, now I know to be aware that if I do use new or malloc somewhere inside of my classes, then I will need to manually take care of their deletion in the class' destructor.

And overall the worst mistake that can happen with a memory problem is my program won't work correctly, not permanent harm to my arduino. lol! :sweat_smile:

Thank you both!!

Just one more thing, dynamic memory allocation is bad on small microcontrollers :frowning:

It's ok to do it in setup() like you did. But if you have to delete and create objects constantly in loop(), you may not have memory leaks, but you will have memory fragmentation (again it doesn't permanently harm your arduino)

1 Like

Ah ok thank you! I will keep an eye out for that :smiley:

That's the standard dogma around here anyway. And perhaps it has some merit for low-memory AVR processors. But the Arduino ecosystem also includes ARM and ESP-based boards. Take a look at source code files for say the ESP8266 Arduino core. The String class is used liberally ... to cite just one instance of dynamic memory allocation.

1 Like

That's certainly the "C++ way". And, the smart pointers will take care of deleting the dynamically allocated object at the proper time. As noted by @Whandall, give your child classes virtual destructors.

As an aside, why are you using std::shared_ptr:

shapesArray.push_back(std::shared_ptr<Circle>(new Circle(5)));

instead of std::unique_ptr?

1 Like

Thank you for your reply! Is it possible to use smart pointers in the Arduino IDE? When I used that bit of code...

std::vector< std::shared_ptr<Shape> > shapesArray;
shapesArray.push_back(std::shared_ptr<Circle>(new Circle(5)));
shapesArray.push_back(std::shared_ptr<Circle>(new Circle(10)));
shapesArray.push_back(std::shared_ptr<Triangle>(new Triangle(10, 5)));
shapesArray.push_back(std::shared_ptr<Rectangle>(new Rectangle(10, 5)));

...the compiler gave me an error that vector is not a member of std. And if I tried using #include it couldn't find any such file or library.

As for why I used std:shared_ptr instead of std:unique_ptr - I have no idea, lol! I haven't used smart pointers before, and it seemed from the articles that unique_ptr was more restrictive, so without knowing the consequences I picked shared_ptr. But I don't fully understand smart pointers yet. :innocent:

It depends, which Arduino board are you trying to compile for?

1 Like

All the code examples I was providing above were while compiling for Arduino Uno, but my target board for my project will ultimately be the Teensy 4.1.

AVR-based boards don't support the STL. Teensy 4.1 does.

#include <vector>
#include <memory>

class Shape {
};

class Circle : public Shape {
  public:
    Circle(uint16_t r) {
    }
};

class Triangle : public Shape {
  public:
    Triangle(uint16_t b, uint16_t h) {
    }
};

class Rectangle : public Shape {
  public:
    Rectangle(uint16_t l, uint16_t w) {
    }
};

std::vector <std::unique_ptr<Shape> > shapesArray;

void setup() {
  Serial.begin(115200);
  delay(1000);

  shapesArray.push_back(std::unique_ptr<Circle>(new Circle(5)));
  shapesArray.push_back(std::unique_ptr<Circle>(new Circle(10)));
  shapesArray.push_back(std::unique_ptr<Triangle>(new Triangle(10, 5)));
  shapesArray.push_back(std::unique_ptr<Rectangle>(new Rectangle(10, 5)));
}

void loop() {
}
1 Like

@arduinoacrobat

So if you want to keep portability of the code to AVR controllers - do not use std:: primitives

1 Like

That's likely irrelevant. If OP is targeting Teensy 4.1 for this project, then an AVR won't cut it anyway.

1 Like

just a general comment :slight_smile:

1 Like

Wow, I had no idea that inclusion of certain C++ libraries were board dependent, rather than being dependent on the Arduino programming language. That's great to know!

It seems for now, choosing a vector of smart pointers over an array of pointers may not matter for my specific example, but if I start using more pointer arrays, or if I need to be able to dynamically add more elements to my array, then the vector will start to look more and more attractive.

This particular project involves multiple stepper motors and servo motors, so the processing speed of the Arduino boards are too slow. So, I don't need to worry too much about portability to a slower board, but at least now I can keep it in mind.

Thank you so much everyone, I really appreciate all your help!

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.