Declaration of a Dynamic Array of Objects

Hi guys,

Embarrassingly, I'm having a little trouble declaring a global array of objects dynamically.

Right now I've gotten the following working:

Sketch

#include "MyClass.h"
#define ROWS 3
MyClass arr[ROWS];

Header

#ifndef MyClass_h
#define MyClass_h
#include "WProgram.h"

class MyClass {
  public:
    MyClass();
    MyClass(int var);
    int getVar();

  private:
    int _var;
};
#endif

You can probably guess what MyClass.cpp file looks like.

However, if I try to do this dynamically,

Sketch

#include "MyClass.h"
#define ROWS 3

MyClass *arr;
void setup() {
  for(int i = 0; i < ROWS; i++) {
    arr[i] = new MyClass;
  }
}

I get an error

error: invalid conversion from 'MyClass *' to 'int'

I was trying to do this on another project of mine, and I also received a

undefined reference to 'operator new[](unsigned int)'

I've done quite a bit of searching earlier and was unable to find anyone stuck at a similar spot.

Many thanks in advance!

"new" doesn't work on the Arduino.

"new" doesn't work on the Arduino.

It's more that new isn't implemented on the Arduino.

You have a separate problem, though.

MyClass *arr;

arr is a pointer to an instance of MyClass. Yet,

for(int i = 0; i < ROWS; i++) {
    arr[i] = new MyClass;
  }

You are treating it as though it pointer to space that can hold ROWS values, even though you haven't allocated that space.

For this simple example, you don't need new. If you have some "real work" that needs it, well, you are out of luck with avr-gcc. unless you write your own new operator (using malloc or some such thing). That might not be as simple as some people think. Also, it's just too easy to create C++ code that requires lots and lots of storage in ways that are not always obvious. Maybe that's why embedded programmers sometimes try to get along without dynamic memory allocation all together. (I wouldn't try implement my own new operator, but that's just me: I'm funny that way.)

Anyhow, maybe you can figure out how implement your stuff without needing it by trying something like the following. (See Footnote.)

// After debugging, you can put the class definition
// and implementation in separate .h and .cpp library
// files.
//
// davekw7x
//


// Class Definition
//
class MyClass {
public:
    MyClass();
    MyClass(int var);
    int getVar() const;
    void putVar(int val);

private:
    int _var;
};

//
// Class implementation
//
// Default constructor sets _var to zero
MyClass::MyClass():_var(0) {}

// Constructor with initializef for _var
MyClass::MyClass(int var):_var(var){}

// Getter function
int MyClass::getVar() const {return _var;}

// Putter function
void MyClass::putVar(int val) {_var = val;}

//
// The Maybe create your own main() instead of this sketch
//

const int ROWS = 3;
MyClass arr[ROWS]; // An array of MyClass objects using default constructor

//
// Initialize the objects in setup()
//
void setup()
{ 
    Serial.begin(9600);
    for (int i = 0; i < ROWS; i++) {
        arr[i].putVar(11*(i+1));
    }
}

//
// Use the objects in loop()
//
void loop()
{
    for (int i = 0; i < ROWS; i++) {
        Serial.print("arr[");
        Serial.print(i);        Serial.print("].getVar() = ");
        Serial.println(arr[i].getVar());
    }
    Serial.println();
    delay(10000);

}

Output:


arr[0].getVar() = 11
arr[1].getVar() = 22
arr[2].getVar() = 33

Since I wanted to test it with a simple Arduino sketch and the Arduino setup() function doesn't take parameters, I had to make the array a global (file-scope, actually) variable. That's one of those irritating little things that drives Big-Time C++ programmers nuts. Of course to do it like "real" C++ programmers would probably do it, you could put the array initialization code (or a function call to do the deed) at the begging of loop(), and then follow that by an infinite loop so that loop() never returns to Arduino's main() function. Then the array could be a local variable inside of Arduino's loop() function.

"Real" C++ programmers' irritation that is often caused by being faced with limited resources, like not enough RAM in these embedded systems to allow them to use oodles and oodles of dynamically allocated memory without worrying about code efficiency, is one of the other things that explains a lot.

Regards.

Dave

Footnote:
This is a very simple example. It won't work if ROWS has to be a variable (not known at compile time). You have to evaluate your design to see whether something like my example would be practical in light of your requirements.

Could you use a sparse array?

(Would this work?...)

MyClass *arr[15];

for(uint16_t i = 0; i <= 15; i++) {
arr = (MyClass *) mallac(sizeof(MyClass));
}

Would this work?

No. What you are doing is allocating (using the wrong function) space for an instance of MyClass, but you are not actually creating an instance of MyClass.

A sparse array is one that has only a few values actually stored in it. The array you have defined is then filled completely. Hardly the definition of sparse.

@ wonginator1221: I think that this is what you are after. This program makes room for the objects, which stores and retrieves an integer.

class myClass {
  int i;
public:
  void init(int I) {
    i = I;
  }
  int show() {
    return i;
  }
};


void setup() {
  
  myClass *p[16]; 

  Serial.begin(9600);
  
  for(uint16_t i = 0; i <= 5; i++) {
    p[i] = (myClass *) malloc(sizeof(myClass));
  } 

  p[0]->init(0);

  p[2]->init(8);

  p[6]->init(7);

  Serial.println(p[0]->show());

  Serial.println(p[2]->show());

  Serial.println(p[6]->show());

}

void loop() {

}
p[i] = (myClass *) malloc(sizeof(myClass));

p now points to a block of space large enough to hold an instance of myClass. It does NOT point to an instance of myClass.

p now points to a block of space large enough to hold an instance of myClass. It does NOT point to an instance of myClass. [/quote]
The code works. I made sure before I posted it.

You declared an array of pointers to myClass, of size 16.

Then, you set the first 6 values to point to dynamically allocated (but not initialized) memory.

Then, you dereference the 1st, 3rd, and 7th pointers. And I'm expected to believe that it worked.

If anyone can explain HOW, I'm all ears.

Well, maybe not all ears. All thumbs, sometimes. :slight_smile:

I think that it works because of the type cast.

But you should probably try it yourself to prove that it works and review text books on malloc() if you are not sure why it is working.

I know what malloc does. It returns a pointer to a block of memory. It does nothing to initialize that memory. The type cast of the return value is required because malloc is just returning a pointer (of type void) to the first memory location allocated.

I'm not too sure what the problem is: You "malloc" some memory space, and then you use it through pointers.

Isn't that what you always do?

If I malloc space to hold an array of doubles, I don't expect the doubles that I want to use to be there after the malloc call.

If I malloc space to hold an array of characters, I don't expect the characters that I want to use to be there after the malloc call.

If I malloc space to hold a class instance, I do not expect the class to have been instance in the allocated space.

I suppose that it is working similar to a dynamically allocated struct (which I'm sure that you have heard about- If not, look for some examples on linked lists).

These work in a similar way: The malloc statement is used, and you can write to any of the elements using the pointer the malloc returned.

@InvalidApple:
I think you're misunderstanding the difference between a class (or object) and a struct.

As PaulS pointed out, malloc'ing some memory and casting the pointer to it does not a class instantiate.
If you doubt me, try calling one of the class methods.

@Groove
That's the thing we are having the discussion about. He says that he did run the sketch, which does call the instance's methods, and that it worked.

I'm having a hard time accepting that.

I'm going to try it at home, tonight.

15 posts in 24 hours! Thanks for all the help.

Personally, I have a Java background, but I'm pretty familiar with C's malloc.

I initially avoided malloc because many C++ "tutorials" suggest staying away from malloc as it is often a source for memory leaks. However, if it's the only way to truly dynamically allocate memory, it shouldn't be an issue.

After digesting InvalidApple's code and the responses generated I have concluded that malloc must call the default constructor for MyClass (without the int parameter).

#define ROWS 3
class MyClass {
  int _var;
  public:
    MyClass() {
      _var = 0;
    };
    MyClass(int a) {
      _var = a;
    };
    void setVar(int a) {
      _var = a;
    }
    int getVar() {
      return _var;
    };
};

MyClass *arr;
void setup() {
  Serial.begin(9600);
  arr = (MyClass *) malloc(sizeof(MyClass) * ROWS);
  for(int i = 0; i < ROWS; i++)
    arr[i].setVar(i + 1);
  
  for(int i = 0; i < ROWS; i++)
    Serial.print(arr[i].getVar());
    //Should return '1 2 3'
}

void loop() {}

Currently I don't have an Arduino to test this on, but I'm pretty sure this works. Multidimensional arrays would just require more 'for' loops.

Now for the real question:
Are objects extremely memory intensive (for an Arduino)? I've created a simple pixel class that lets me easily set the color of an RGB LED and I plan to create up to 64 of these 'Pixel' objects. Are there more efficient structures for this type of application?

Thanks again!

Are objects extremely memory intensive (for an Arduino)?

That depends on haw many, and what type of, fields and methods the class has.

I have concluded that malloc must call the default constructor for MyClass

The malloc function does not call the constructor. There may, or may not be some other mechanism that does.

I want to dump the assembler code that is generated, and see what it shows.

The code works. I made sure before I posted it.

Your testing is far from complete.

Add and call several virtual functions and please let us know what happens.