Problem with for() loop through array of objects

Hello everybody. I'm new to the forum and this is my first post.
I'm also new to Arduino and C++, but have done programming in the (distant) past.

I have a poc sketch for a model train layout, in which I read three ir-sensors per track ('spoor' in the sketch) to establish the status (occupied or not). Tracks and sensors are defined als classes. For looping through the sensors and tracks respectively I have put them in arrays.
The problem is, I think, in the update-loop in the main sketch. Instead of reading the sensors of track[0] in the first pass, it gives the result of track[1]. In the second pass it gives the results of non-existing sensors/pins.

I must have made a mistake, but I can't figure it out. BTW: the sensors are working correctly. I wrote a short sketch to test them, also using an array for looping through the sensors directly, without the spoor class, and the results were as expected.

Main sketch:



#include "globals.h"
#include "OpstelSpoor.h"
#include "LibPrintf.h"

// Forward declaration: waits until key pressed for debugging
void waitForKeyPress();

const byte numTracks = 2; // debug, moet naar globals.h

// define the pins, corresponding to each track
const byte inrijPin[numTracks] = { A0, A1 };   // testaantal, aanpassen voor def
const byte bezetPin[numTracks] = { 2, 3 };     // testaantal, aanpassen voor def
const byte uitrijPin[numTracks] = { 11, 12 };  // testaantal, aanpassen voor def


// ###############  instantiate the tracks   ######################################################

OpstelSpoor spoor[numTracks] = {
  OpstelSpoor(inrijPin[0], bezetPin[0], uitrijPin[0]),
  OpstelSpoor(inrijPin[1], bezetPin[1], uitrijPin[1]),
};

// ################################################################################
void setup() {

  Serial.begin(115200);

  for (byte i = 0; i < numTracks; i++) {
    //debug
    printf("[MN-%.i] Iteratie spoor[i].begin: i = %i.\n", __LINE__, i);
    waitForKeyPress();
    //\debug
    spoor[i].begin();
  }
  printf("[MN-%.i] Setup klaar.\n", __LINE__);  //debug
}  //setup

// ################################################################################
void loop() {
  // check de bezetting van een spoor
  for (byte i = 0; i < numTracks; i++) {
    printf("[MN-%i] Status van spoor: %i.\n", __LINE__, i);  //debug
    spoor[i].update();                              // lees sensors en bepaal status
    printf("[MN-%.i] Spoor %i heeft status %i.\n", __LINE__, i, spoor[i].getStatus());
    Serial.println("");
  }
  waitForKeyPress();
}  // loop


void waitForKeyPress() {
  while (!Serial.available()) {
    // Do nothing
    delay(100);
  }

  while (Serial.available()) {
    Serial.read();
  }
}  // waitForKeyPress

track class header file:

/*
Bezetmelding voor doorgaand spoor op schaduwstation
20240123  start

*/

#ifndef sp_h
#define sp_h

#include "globals.h"
#include "Arduino.h"
#include "Sensor.h"

class OpstelSpoor {
public:
  OpstelSpoor(int sensorIn, int sensorBezet, int sensorUit);  //
  void begin();
  void update();
  uint8_t getStatus();  // of de update zelf een return laten geven?

private:
  Sensor _sensorIn;
  Sensor _sensorBezet;
  Sensor _sensorUit;
  uint8_t _bezetMelding;
  uint8_t _status;
  void _waitForKeyPress();
  Sensor _sensor[];

protected:
};

#endif

track class cpp file:

/*
Bezetmelding voor doorgaand spoor op schaduwstation
20240123  start

*/

#include "globals.h"
#include "Arduino.h"
#include "OpstelSpoor.h"
#include "Sensor.h"
#include "LibPrintf.h"

const uint8_t _numSensors = 3;

enum {
  KAPOT,   // O
  VRIJ,    // 1
  INRIJ,   // 2
  BEZET,   // 3
  UITRIJ,  // 4
  ALARM    // 5
};


OpstelSpoor::OpstelSpoor(int sensorIn, int sensorBezet, int sensorUit)
  : _sensorIn(sensorIn), _sensorBezet(sensorBezet), _sensorUit(sensorUit) {}  //geldt voor doorgaande opstelsporen

void OpstelSpoor::begin() {
  // plaats de sensors in array

  //debug
  printf("[OS-%.i] Waarde _sensorIn: %i, _sensorBezet: %i, _sensorUit: %i.\n", __LINE__,
         _sensorIn.getPinNumber(), _sensorBezet.getPinNumber(), _sensorUit.getPinNumber());
  _waitForKeyPress();
  //\debug

  Sensor _sensor[_numSensors] = {
    _sensorIn,
    _sensorBezet,
    _sensorUit
  };

  //debug
  printf("[OS-%.i] Waarde _sensorIn: %i, _sensorBezet: %i, _sensorUit: %i.\n", __LINE__,
         _sensor[0].getPinNumber(), _sensor[1].getPinNumber(), _sensor[2].getPinNumber());
  _waitForKeyPress();
  //\debug

  // start de sensors
  for (byte i = 0; i < _numSensors; i++) {
    _sensor[i].begin();
  }

  //initieer de bezetMelding
  _bezetMelding = 0;
};  //Opstelspoor::begin


void OpstelSpoor::update() {
  for (byte i = 0; i < _numSensors; i++) {
    bitWrite(_bezetMelding, i, _sensor[i].update());

    // debug
    printf("[OS-%.i] Sensor %i = %i met bezetmelding = %i.\n", __LINE__, i, _sensor[i].update(), _bezetMelding);
    printf("[OS-%.i] Pin: %i.\n", __LINE__, _sensor[i].getPinNumber());
    delay(1000);
    //\debug
  }

  //debug
  _waitForKeyPress();
  //\debug

  // vertaal de bezetmelding naar de status

  switch (_bezetMelding) {
    case 0:
      _status = VRIJ;
      break;
    case 4:
      _status = INRIJ;
      break;
    case 6:
      _status = BEZET;
      break;
    case 2:
      _status = BEZET;
      break;
    case 3:
      _status = UITRIJ;
      break;
    case 1:
      _status = UITRIJ;
      break;
    case 7:
      _status = ALARM;
      break;
    case 5:
      _status = ALARM;
      break;
    default:
      _status = KAPOT;
      break;
  }

  //zet de bezetMelding terug op 0 voor de volgende cyclus
  _bezetMelding = 0;
}  //Opstelspoor::update

uint8_t OpstelSpoor::getStatus() {
  return _status;
}  //OpstelSpoor::getStatus

void OpstelSpoor::_waitForKeyPress() {
  while (!Serial.available()) {
    // Do nothing
    delay(100);
  }

  while (Serial.available()) {
    Serial.read();
  }
}  //OpstelSpoor::_waitForKeyPress

Output on the serial monitor for one pass through:

:{[MN-33] Iteratie spoor[i].begin: i = 0.
[OS-32] Waarde _sensorIn: 14, _sensorBezet: 2, _sensorUit: 11.
[OS-44] Waarde _sensorIn: 14, _sensorBezet: 2, _sensorUit: 11.
[MN-33] Iteratie spoor[i].begin: i = 1.
[OS-32] Waarde _sensorIn: 15, _sensorBezet: 3, _sensorUit: 12.
[OS-44] Waarde _sensorIn: 15, _sensorBezet: 3, _sensorUit: 12.
[MN-38] Setup klaar.
[MN-45] Status van spoor: 0.
[OS-64] Sensor 0 = 0 met bezetmelding = 0.
[OS-65] Pin: 15.
[OS-64] Sensor 1 = 0 met bezetmelding = 0.
[OS-65] Pin: 3.
[OS-64] Sensor 2 = 0 met bezetmelding = 0.
[OS-65] Pin: 12.
[MN-47] Spoor 0 heeft status 1.

[MN-45] Status van spoor: 1.
[OS-64] Sensor 0 = 0 met bezetmelding = 0.
[OS-65] Pin: 15258.
[OS-64] Sensor 1 = 1 met bezetmelding = 2.
[OS-65] Pin: -20021.
[OS-64] Sensor 2 = 1 met bezetmelding = 6.
[OS-65] Pin: -28707.
[MN-47] Spoor 1 heeft status 3.

the _sensor private instance variable

is not then same as the local array you define in OpstelSpoor::begin()

which will get removed at the end of the function

You might want to read about scope

Thanks for pointing that out. I'm aware of scoping, but I thought that when you declare a private variable in the header, you can use it in the functions in cpp. I don't fully understand, why the _sensor[] array in Opstelspoor::begin() is different from, say, _sensorIn.
I suppose I have to move the value assignment to _sensor[] to the constructor, but at first tries this gives a lot of compiler errors. Have to puzzle on that one...

this is true but here you define a new one as you repeated the type in front of your definition/

also in the class definition you don't give a size to your array

does it even compile?

When I leave out the type, I get an error in compiling. So I put it in front of it.

Yes, this compiles. Not however when I give it a size. Also it is being handled in the update() function, as you can see in the output. Albeit wrongly.

I'm really at a loss about how to define an array of member objects within an object.

Oddly, "flexible array" members are allowed if specified as the last member in a class / struct. However, there are apparently limitations on then including them in another class / struct. And, I imaging, making an array of them.

But, my quick internet search didn't turn up the proper method for actually using them.

OK

indeed, you need at least one other attribute but I don't think flexible array members are supported in C++, may be a GCC thingy to not crash because it's supported in C to access variable length data?

You can't do anything with them.

--

@pet_jk, instead of

Sensor _sensor[];

you might want to have a pointer to an array of Sensor items and another instance variable that says how many you have

Sensor * _sensors;
size_t _sensorsCount; 

or as it seems you know you will have only 3 sensors in that array

Sensor* _sensor[3];

and in the begin() function do

void OpstelSpoor::begin() {
  // plaats de sensors in array
    _sensor[0] = &_sensorIn;
    _sensor[1] = &_sensorBezet;
    _sensor[2] = &_sensorUit;

•••

  };


I made the array an array of pointers, so you would use -> instead of . when using the array.

Alternatively you don't need to duplicate the Sensors to have the array, just don't create those instances variables

  Sensor _sensorIn;
  Sensor _sensorBezet;
  Sensor _sensorUit;

and only have

  Sensor _sensor[3];

the constructor would then instantiate the sensors within the array as part of the initialiser list

OpstelSpoor::OpstelSpoor(int sensorIn, int sensorBezet, int sensorUit)
  : _sensor{sensorIn, sensorBezet, sensorUit} {}  

of course you'll have to know that _sensorIn is the first entry as that name is gone (same for the others entries)

Thanks, this gives me some things to try. Will be later tonight.

Brilliant. I've implemented this last alternative and it does the trick, also in an elegant way that I can easily understand. Working with pointers still is a little outside my comfort zone.
Thanks.

Great - have fun