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
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
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.
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)
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
When the arduino is restarted, the memory is wiped and clean as new, your objects are created as if they never existed before
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!
Just one more thing, dynamic memory allocation is bad on small microcontrollers
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)
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.
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.
...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.
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.
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!