Iterate trough objects of different types

Is there a way of having a list (array, vector, whatever...) containing objects of different types and loop trough each object and call a method on it? All Objects have the same abstract parent class + the virtual method implemented.

What I already tried with arrays:

  • No parent class but all objects of the same type => works
  • Parent class abstract => can not define an array of abstract type
  • Parent class not abstract, array of type of parent class => method of parent class is called
/**
 * Parent class
 */
class Sensor {
  public:
    virtual long getValue();
};

long Sensor::getValue() {
  Serial.println("method call to parent class");
}

/**
 * Child class 1
 */
class SensorAnalog: public Sensor {
   public:
     long getValue();
};

long SensorAnalog::getValue() {
  Serial.println("method call to SensorAnalog");
}

/**
 * Child class 2
 */
class SensorDigital: public Sensor {
   public:
     long getValue();
};

long SensorDigital::getValue() {
  Serial.println("method call to SensorDigital");
}

// ============================

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

  Sensor mySensors[2];                // array
  SensorAnalog s0 = SensorAnalog();   // analog sensor object
  SensorDigital s1 = SensorDigital(); // digital sensor object

  // put objects into array
  mySensors[0] = s0;
  mySensors[1] = s1;
  
  // loop through objects
  for (int i = 0; i<2; i++) {
    mySensors[i].getValue();
  }
 
}

void loop() {
}

Here's a way:

/**
 * Parent class
 */
class Sensor {
  public:
    virtual long getValue();
};

long Sensor::getValue() {
  Serial.println("method call to parent class");
}

/**
 * Child class 1
 */
class SensorAnalog: public Sensor {
   public:
     long getValue();
};

long SensorAnalog::getValue() {
  Serial.println("method call to SensorAnalog");
}

/**
 * Child class 2
 */
class SensorDigital: public Sensor {
   public:
     long getValue();
};

long SensorDigital::getValue() {
  Serial.println("method call to SensorDigital");
}

// ============================

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

  Sensor* mySensors[2];                // array
  SensorAnalog s0 = SensorAnalog();   // analog sensor object
  SensorDigital s1 = SensorDigital(); // digital sensor object

  // put objects into array
  mySensors[0] = &s0;
  mySensors[1] = &s1;
  
  // loop through objects
  for (int i = 0; i<2; i++) {
    mySensors[i]->getValue();
  }
 
}

void loop() {
}
  1. thanks a lot

Now I'm at the point of some refactoring and I'd like to put all the handling into an own class.

/**
 * Parent class
 */
class Sensor {
  public:
    virtual long getValue();
};

long Sensor::getValue() {
  Serial.println("method call to parent class");
}

/**
 * Child class 1
 */
class SensorAnalog: public Sensor {
   public:
     long getValue();
};

long SensorAnalog::getValue() {
  Serial.println("method call to SensorAnalog");
}

/**
 * Child class 2
 */
class SensorDigital: public Sensor {
   public:
     long getValue();
};

long SensorDigital::getValue() {
  Serial.println("method call to SensorDigital");
}


/**
 * Sensor controller
 */
class SController {
  public:
    SController();
    void addSensor(Sensor s);
    void process();
  private:
    Sensor* mySensors[2];
    int sensorCount;
};

SController::SController() {
  sensorCount = 0;
}

void SController::addSensor(Sensor s) {
  mySensors[sensorCount++] = &s;
}

void SController::process() {
  for (int i = 0; i<sensorCount; i++) {
    mySensors[i]->getValue();
  }
}



// ============================

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

  SController myController = SController();
  SensorAnalog s0 = SensorAnalog();   // analog sensor object
  SensorDigital s1 = SensorDigital(); // digital sensor object


  // put objects into array
  myController.addSensor(s0);
  myController.addSensor(s1);
  
  // loop through objects
  myController.process();
 
}

void loop() {
}

Again the methods of the parent class' getValue() method is called. Why?

Do I have to pass only pointers to the addSensor method? If so, how do I put the pointer into an array (get a error: "cannot convert 'Sensor' to 'Sensor*' in assignment")? Can anyone recomend a book/tutorial explaining this kind of stuff? I'm really starting to hate C :wink:

You need to pass parameter 's' of addSensor by address or by reference. If you pass it by value as you are doing, the copy constructor for class Sensor will be called and the function will be passed a temporary Sensor object that has been cloned from a cut-down version of the original. So use one of the following, and change the signature of the function declaration to match:

void SController::addSensor(Sensor &s) {
  mySensors[sensorCount++] = &s;
}
void SController::addSensor(Sensor *ps) {
  mySensors[sensorCount++] = ps;
}

Ok, think I got it :slight_smile:

/**
 * Parent class
 */
class Sensor {
  public:
    virtual long getValue();
};

long Sensor::getValue() {
  Serial.println("method call to parent class");
}

/**
 * Child class 1
 */
class SensorAnalog: public Sensor {
   public:
     long getValue();
};

long SensorAnalog::getValue() {
  Serial.println("method call to SensorAnalog");
}

/**
 * Child class 2
 */
class SensorDigital: public Sensor {
   public:
     long getValue();
};

long SensorDigital::getValue() {
  Serial.println("method call to SensorDigital");
}


/**
 * Sensor controller
 */
class SController {
  public:
    SController();
    void addSensor(Sensor* s);
    void process();
  private:
    Sensor* mySensors[2];
    int sensorCount;
};

SController::SController() {
  sensorCount = 0;
}

void SController::addSensor(Sensor* s) {
   mySensors[sensorCount++] = s;
}

void SController::process() {
  for (int i = 0; i<sensorCount; i++) {
    mySensors[i]->getValue();
  }
}



// ============================

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

  SController myController = SController();
  SensorAnalog s0 = SensorAnalog();   // analog sensor object
  SensorDigital s1 = SensorDigital(); // digital sensor object


  s0.getValue();
  // put objects into array
  myController.addSensor(&s0);
  myController.addSensor(&s1);

  
  // loop through objects
  myController.process();
 
}

void loop() {
}

sui:

void setup() {

Serial.begin(9600);

SController myController = SController();
  SensorAnalog s0 = SensorAnalog();   // analog sensor object
  SensorDigital s1 = SensorDigital(); // digital sensor object

Warning, though: Those SensorAnalog/SensorDigital variables are only valid in scope of the setup() function. When setup() ends, s0 and s1 dies -- but your collection still has pointers to those (now dead) objects! Which will point into random addresses on your stack. Which will probably cause all kinds of problems once you start putting stuff into your loop() function.
Much better to create the objects as globals.
Also, you generally don't initialize objects through object assignment copy -- that's pretty inefficient. If the object has no arguments to the constructor, just declare it without parentheses. If the object requires arguments, pass them, with parentheses.

SensorAnalog s0;
SensorDigital s1;
SensorWithArgument s2(45);

void setup() {
...

Note that, if you pass empty parentheses after your declarations, they suddenly become forward declarations of functions, which is not at all what you want. C++ gotcha!

Finally, if you want to initialize objects inside setup() in an order you control, you can do this (using something called "placement new"):

inline void *new(size_t, void *arg) { return arg; }

char Sensor0buf[sizeof(SensorAnalog)];
char Sensor1buf[sizeof(SensorDigital)];

void setup() {
  Sensor *s0 = new(Sensor0buf) SensorAnalog();
  Sensor *s1 = new(Sensor1buf) SensorDigital();
  ...
}

At that point, the values that s0 and s1 have will point to valid objects even after setup() has exited, because the storage is global, and the objects creating in the storage have not been destructed.