Instantiating fairly large array of objects. And then, two-dimensional...

Hello all--

Earlier, I got help here on how to instantiate objects. Koepel helped me out with the syntax.

Now I'm trying to move further to make arrays of those objects.

In my same program, code below, that creates objects of class Automobile, I now have it so that it creates an array of 3 of those Automobiles. My automobile class needs 3 parameters passed to it, so I've successfully got it instantiating an array of 3 of the Automobiles, by using this statement out in the global section of the code:

Automobile autos[3] = {Automobile(40,0,40), Automobile(5,75,5), Automobile(20,55,50)};

So I have 2 more annoying questions:

1-- The above works great, but what if I wanted to instantiate 200 Automobiles all at once? Naturally I don't want to type an ultra long list within the brackets... is there a way to use a loop to do this? I've done this in Python a long time ago using a "list comprehension" but don't know the best way to do it in Arduino C++.

2-- Then, to go further (and as far as I intend to go...) ... I'd like to set up a 2 dimensional array, not just a one-dimensional one. (My ultimate goal is to make a simple game with a grid of objects. But for this example, I guess we're imagining a 2 dimensional grid of Automobiles...)
... I know that the way to work with 2-dimensional arrays C++ is autos[3][5] ... So once I have succeeded instantiating it, I'm guessing I will say things like autos[3][5].acclerate()
... BUT, just like the question 1 above, I don't know the syntax to instantiate the 2-dimensional array to begin with (that it, without typing out the long list of nested { { },{ },{ }, ... things...

Here is my working code with only the above three-automobile instantiating I mention above...

class Automobile {
  public:
    Automobile(int gaslevel, int velocity, int tanksize);
    void accelerate();
    void halt();
    void decelerate();
    void filltank();
    void consumegas();
    int checkspeed();
    int checkgas();
  private:
    int _gaslevel;
    int _velocity;
    int _tanksize;
};

// creating functions for the object... in other words object members that are METHODS
Automobile::Automobile(int gaslevel, int velocity, int tanksize) {
  _gaslevel = gaslevel;
  _velocity = velocity;
  _tanksize = tanksize;
}

void Automobile::accelerate() {
  _velocity++;
}

void Automobile::halt() {
  _velocity = 0;
}

void Automobile::decelerate() {
  _velocity--;
}

void Automobile::filltank() {
  _gaslevel = _tanksize;
}

void Automobile::consumegas() {
  _gaslevel--;
}

int Automobile::checkspeed() {
  return _velocity;
}

int Automobile::checkgas() {
  return _gaslevel;
}

// INSTANTIATING THE ARRAY IN THE SIMPLE WAY...

Automobile autos[3] = {Automobile(40,0,40), Automobile(5,75,5), Automobile(20,55,50)};


void setup() {
  Serial.begin(9600);
  Serial.println("------------------");
}


void loop() {
  
  int r = random(1,4);
  if ( r == 1) {
    autos[1].accelerate();
  }
  else if (r== 2) {
    autos[1].decelerate();
  }

  autos[1].consumegas();

  if (autos[1].checkgas() < 1) {
    autos[1].halt();
    autos[1].filltank();
  }
  
  Serial.print(" velocity=");
  Serial.print(autos[1].checkspeed());
  Serial.print(" gaslevel=");
  Serial.print(autos[1].checkgas());
  Serial.println();

}

Huge thanks for any ideas --

Eric

Hi Eric, what type of Arduino are you using for this? 200 of your simple objects above will fill more than half the ram memory of an Uno.

If you want to avoid instantiating a 1-D/2-D array of objects because of all the typing and so on, the are two ways I can think of to avoid this.

One is to use a spreadsheet on the pc to take the parameters of each object and format them into text that you can then copy & paste into the IDE. Make a separate tab for it if you like so you don't have to scroll through it while writing the rest of your code.

The other is to instantiate the objects at run-time. To do this, your object array will need to be an array of pointers to the objects rather than the objects themselves. Then in setup() you can create the objects in a loop. When you access the objects in the rest of your code, you will need to treat them as references. This will of course require even more ram memory...

may be that example would help:

class Automobile
{
  public:
    // instance variables
    uint8_t _a;
    uint8_t _b;

    // constructor
    Automobile(uint8_t a, uint8_t b) : _a(a), _b(b) {}

    void printout() {
      Serial.print(F("a = ")); Serial.print(_a);
      Serial.print(F(", b = ")); Serial.println(_b);
    }
};

const uint8_t nbRow = 2, nbCol = 3;

// THIS IS THE MANUAL WAY
Automobile garagePreDefined[nbRow][nbCol] = {
  {Automobile(0, 0), Automobile(0, 1), Automobile(0, 2)},
  {Automobile(1, 0), Automobile(1, 1), Automobile(1, 2)}
};

// WE KEEP POINTERS TO THE INSTANCES FOR THE "DYNAMIC" WAY (hence the *)
Automobile* garageDynamic[nbRow][nbCol];

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

  // for the garage with instances defined in the code
  for (uint8_t r = 0; r < nbRow; r++)
    for (uint8_t c = 0; c < nbCol; c++) {
      Serial.print(F("garagePreDefined["));
      Serial.print(r);
      Serial.print(F("]["));
      Serial.print(c);
      Serial.print(F("] => "));
      garagePreDefined[r][c].printout();
    }

  Serial.println(F("-------------------------"));

  // instantiate the "dynamic" garage elements
  for (uint8_t r = 0; r < nbRow; r++)
    for (uint8_t c = 0; c < nbCol; c++)
      garageDynamic[r][c] = new Automobile(r * 10, c * 10);

  // print the dynamic garage elements
  for (uint8_t r = 0; r < nbRow; r++)
    for (uint8_t c = 0; c < nbCol; c++) {
      Serial.print(F("garageDynamic["));
      Serial.print(r);
      Serial.print(F("]["));
      Serial.print(c);
      Serial.print(F("] => "));
      garageDynamic[r][c]->printout(); // NOTE THE -> operator as we now have pointers to Automobile Instances in the array
    }

}

void loop() {}

You should see this in the console set at 115200 bauds

[color=purple]garagePreDefined[0][0] => a = 0, b = 0
garagePreDefined[0][1] => a = 0, b = 1
garagePreDefined[0][2] => a = 0, b = 2
garagePreDefined[1][0] => a = 1, b = 0
garagePreDefined[1][1] => a = 1, b = 1
garagePreDefined[1][2] => a = 1, b = 2
-------------------------
garageDynamic[0][0] => a = 0, b = 0
garageDynamic[0][1] => a = 0, b = 10
garageDynamic[0][2] => a = 0, b = 20
garageDynamic[1][0] => a = 10, b = 0
garageDynamic[1][1] => a = 10, b = 10
garageDynamic[1][2] => a = 10, b = 20
[/color]

I can't thank you enough for this help. Completely helpful to me. Having it all in your full Garage program example is fantastic.

I think your Garage example program is basically doing the technique that PaulRB mentioned in his last paragraph.

To be honest, I've never felt comfortable with pointers, they are pretty mind bending. BUT, the fact is that having this concrete example will be the way to finally wrap my head around it.

THANKS!!!

Eric

p.s. about Paul's memory comment... it may be that I may find memory an issue sometime. but to be honest, this is more for boosting my programming skills. It's on an Uno at this early moment... I could switch it to a Mega later on. Or choose a smaller number of objects... we'll see!

estephan500:
My automobile class needs 3 parameters passed to it, ....

Naturally I don't want to type an ultra long list within the brackets...

Those two statements contradict each other. If each instance needs its own set of arguments passed to the constructor, then (generally speaking) those arguments need to be typed in by someone. You're the only one who knows what they are. So, that someone is you.

@J-M-L's example avoided the problem (I did say generally speaking) by computing the arguments based on each element's index. If you can do that with your class, then fine. But, if the actual arguments are arbitrary and can't be computed at run time, then you'll have to type them at one point or another.

I've used @PaulRB's spreadsheet idea before and it works fine. But, you still have to type them into the spreadsheet and eventually format them with the proper curly brackets and commas.

You could store them on an SD card and read them it at run time in nested loops. That would require creating your object array dynamically. AND, you're still going to have to create that SD card file by typing in the numbers!!!

So, I don't see how you're going to avoid typing the constructor arguments (in some form) at least once.

gfvalvo:
So, I don't see how you're going to avoid typing the constructor arguments (in some form) at least once.

This is true.

The "dynamic" approach is useful is you want to save states of your objects for example in EEPROM or SD card and start back from those values next time you boot.

The approach would be if there are no values in the persistent storage, establish them from a default list (this is the boring part where you type those things) and then load whatever is in persistent storage whilst instantiating the objects.

@estephan500 --> if you really need more memory, consider an ESP32 instead of a MEGA (unless you also need plenty of pins). if will offer more memory, will be much faster and brings WiFi or Bluetooth if you need that too.

Hi all --

-- Actually, in the games I'll likely be building, it's totally likely that the initial values for my big array of objects will be populated either as random numbers, or all will get identical constants to start with before simulations start to change them, and/or will be based on the loop indexes. So the dynamic approach is fine for me (but hearing your excel, SDRAM, etc ideas are totally informative.)

-- Very funny: I was doing this early testing on an old Uno I had... but my eventual goal was to hopefully implement on a feather HUZZAH... I had assumed the Huzzah had modest memory :slight_smile: ... but experimentally uploading my program there ten minutes ago I notice it has 40x the memory for variables than my Uno. Directly after that, I then read JML's post recommending that I try an ESP32. So I guess we all definitely in sync!

-- By the way, JML's code was so clear, I had my program running with a 2 dimensional array maybe 5 minutes after sitting down to change it. Amazing. And my actual true understanding of the pointers has now risen to 45%. So, thanks again!!

Eric

glad it was helpful!

If you wanted to get really slick, you could read the array size from an SD card file as well as its contents and create it all dynamically. I don't have an SD card connected right now, so the code below simulates that using random(). The bit with Serial.available() at the start allows you to randomize the random number generator by waiting an arbitrary amount of time before starting. Otherwise random will return the same sequence every time.

#include "Arduino.h"

class myClass {
  public:
    myClass(uint8_t p) :
      parameter(p) {
    }
    void printout() {
      Serial.println(parameter);
    }
  private:
    uint8_t parameter;
};

myClass ***myClassArray;
uint8_t nRows;
uint8_t nCols;

void setup() {
  Serial.begin(115200);
  delay(1000);
  Serial.println("Starting, press enter to continue");
  while (!Serial.available()) {
  }
  while (Serial.available()) {
    Serial.read();
  }
  randomSeed(micros());

  nRows = random(2, 6);   // replace with SD file read
  nCols = random(2, 6);   // replace with SD file read

  Serial.print("Creating ");
  Serial.print(nRows);
  Serial.print(" X ");
  Serial.print(nCols);
  Serial.println(" Array of myClass Objects");

  myClassArray = new myClass **[nRows];
  for (uint8_t row = 0; row < nRows; row++) {
    myClassArray[row] = new myClass *[nCols];
  }

  for (uint8_t row = 0; row < nRows; row++) {
    for (uint8_t col = 0; col < nCols; col++) {
      myClassArray[row][col] = new myClass(random(0, 100));   // replace random with SD file read
    }
  }

  for (uint8_t row = 0; row < nRows; row++) {
    for (uint8_t col = 0; col < nCols; col++) {
      Serial.print(F("myClassArray["));
      Serial.print(row);
      Serial.print(F("]["));
      Serial.print(col);
      Serial.print(F("] => "));
      myClassArray[row][col]->printout();
    }
  }
}

void loop() {
}

you guys definitely push the limits... well, I'll get my thing going and get comfortable with the general workings of it, and then I can consider optimizing it like this...

thx!!
eric

If the parameters change at runtime then consider writing your class with a .begin() method.

You see this in a lot of Arduino classes because the class needs some other object like Serial which is not guaranteed to be instantiated before your global declaration.