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() {
}
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
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:
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.
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"):
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.