array of base class, populate with derived class (arduino::ESP2866)

I trying to i populate a base class with derived classes. I was able to populate the object, but when I try to access it, my ESP restarted. This line is triggered a reset:

tBuf = IOs[i]->printState() ;   //  ESP2866 wdt reset!

But I think most probably made a mistake with the populate:

GPIO_NONE *IOs[PIN_NUMBER];
GPIO_NONE temp_GPIO = GPIO_NONE();
temp_GPIO.setPin(PINS[i],i);
IOs[i]= &temp_GPIO;

Unfortunately I cannot figure it out where I made a mistake :frowning:.

here are the relevant part:

#include <functional>
#include <memory>

#include <stdint.h>
#include <Io_hand.h>
#import <Arduino.h>
#include <PubSubClient.h>

const uint8_t PIN_NUMBER = 11;
uint8_t PINS[PIN_NUMBER] = {16,5,4,0,2,14,12,13,15,3,1};

PubSubClient Pclient;


void GPIO_NONE::setPin(uint8_t _pin, uint8_t _pin_gpio_number)
{
  pinMode(pin, OUTPUT);
  pin = _pin;
  pin_gpio_number = _pin_gpio_number;
  
  //Pclient.publish("sensor/Dx", "none", true); 
}

String GPIO_NONE::printState()
{
  String buf = "NONE GPIO";
  Serial.println(buf);
  buf =+ pin_gpio_number;
  Serial.println(buf);
  Serial.println(buf);
  return ( buf );
}

void GPIO_IN::setPin(uint8_t _pin, uint8_t _pin_gpio_number)
{
  pinMode(pin, INPUT);
  pin = _pin;
  pin_gpio_number = _pin_gpio_number;
  //Pclient.subscribe("homeassistant/switch1");
}

String GPIO_IN::printState()
{
  String buf = "IN GPIO";
  buf =+ pin_gpio_number;
  buf =+ " ";
  buf =+ digitalRead(pin);
  Serial.println(buf);
  return ( buf );
}

std::unique_ptr<GPIO_NONE[]> IOs; //= std::make_unique<GPIO_NONE[]>(PIN_NUMBER);

void add_io_none(int i){
	IOs[i]= * new GPIO_NONE();
	IOs[i].setPin(PINS[i],i);
	
}  
void add_io_in(int i){
	IOs[i]= * new GPIO_IN();
	IOs[i].setPin(PINS[i],i);
} 


void Io_hand_init(io_type pins_type[], PubSubClient _Pclient)
{
  //std::make_unique<GPIO_NONE*[]> (PIN_NUMBER);
  Serial.println("Io_hand_init");
  Pclient = _Pclient;
  for(uint8_t i=0; i<PIN_NUMBER; i++){
	Serial.println(i);
    switch (pins_type[i])
    {
    case IO_NONE:
	    add_io_none(i);	
		break;
	case IO_IN:
	    add_io_in(i);
		break;
	case IO_OUT:
		break;
    case IO_DHT11:
		break;		
	case IO_DHT22:
		break;	

	}
	
  }

}

String printGPIO(int arg_cnt, char **args)
{
  String buf = "";
  String tBuf;
  for(uint8_t i=0; i<PIN_NUMBER; i++){
    Serial.println(i);
	tBuf = IOs[i].printState() ;
	buf+=tBuf;
	Serial.println(buf);
  }
  return ( buf );
}

Impossible to answer without seeing your entire code, but it seems like you are saving a pointer to an object on the stack, and then use it after the original object has gone out of scope and has been destructed.

Either use static objects, or create them dynamically using an array of std::unique_ptr<GPIO_NONE>.

Pieter

Thank you Pieter.

Unfortunately i need to create the object's dynamically so i need to go with the second option.
I trying to figure out how this std::unique_ptr work, i never use it before.

I think:

  1. need to include the following 2 library:
    ** #include **
    ** #include **

  2. create the smart pointer array
    ** std::unique_ptr<GPIO_NONE[]> IOs;**

  3. add the necessary child objects to the array
    IOs[i ]= * new GPIO_NONE();
    IOs[i+1]= * new GPIO_IN();

  4. invoke function's
    IOs_.setPin(PINS*,i);[/b]*
    Unfortunately now freezing during the initialization :(.
    so i didn't get really how this smart pointer array will work_

```
*#include
#include

#include <stdint.h>
#include <Io_hand.h>
#import <Arduino.h>
#include <PubSubClient.h>

const uint8_t PIN_NUMBER = 11;
uint8_t PINS[PIN_NUMBER] = {16,5,4,0,2,14,12,13,15,3,1};

PubSubClient Pclient;

void GPIO_NONE::setPin(uint8_t _pin, uint8_t _pin_gpio_number)
{
  pinMode(pin, OUTPUT);
  pin = _pin;
  pin_gpio_number = _pin_gpio_number;
 
  //Pclient.publish("sensor/Dx", "none", true);
}

String GPIO_NONE::printState()
{
  String buf = "NONE GPIO";
  Serial.println(buf);
  buf =+ pin_gpio_number;
  Serial.println(buf);
  Serial.println(buf);
  return ( buf );
}

void GPIO_IN::setPin(uint8_t _pin, uint8_t _pin_gpio_number)
{
  pinMode(pin, INPUT);
  pin = _pin;
  pin_gpio_number = _pin_gpio_number;
  //Pclient.subscribe("homeassistant/switch1");
}

String GPIO_IN::printState()
{
  String buf = "IN GPIO";
  buf =+ pin_gpio_number;
  buf =+ " ";
  buf =+ digitalRead(pin);
  Serial.println(buf);
  return ( buf );
}

//GPIO_NONE *IOs[PIN_NUMBER];
std::unique_ptr<GPIO_NONE[]> IOs; //= std::make_unique<GPIO_NONE[]>(PIN_NUMBER);

void add_io_none(int i){
IOs[i]= * new GPIO_NONE();
IOs[i].setPin(PINS[i],i);


void add_io_in(int i){
IOs[i]= * new GPIO_IN();
IOs[i].setPin(PINS[i],i);
}

/*
GPIO_NONE *IOs[PIN_NUMBER];

void add_io_none(int i){
GPIO_NONE temp_GPIO = GPIO_NONE();
temp_GPIO.setPin(PINS[i],i);
IOs[i]= &temp_GPIO;

void add_io_in(int i){
GPIO_IN temp_GPIO = GPIO_IN();
temp_GPIO.setPin(PINS[i],i);
IOs[i]= & temp_GPIO;
}

/
void Io_hand_init(io_type pins_type[], PubSubClient _Pclient)
{
  //std::make_unique<GPIO_NONE
[]> (PIN_NUMBER);
  Serial.println("Io_hand_init");
  Pclient = _Pclient;
  for(uint8_t i=0; i<PIN_NUMBER; i++){
Serial.println(i);
    switch (pins_type[i])
    {
    case IO_NONE:
    add_io_none(i);
break;
case IO_IN:
    add_io_in(i);
break;
case IO_OUT:
break;
    case IO_DHT11:
break;
case IO_DHT22:
break;

}

}

}

String printGPIO(int arg_cnt, char **args)
{
  String buf = "";
  String tBuf;
  for(uint8_t i=0; i<PIN_NUMBER; i++){
    Serial.println(i);
tBuf = IOs[i].printState() ;
buf+=tBuf;
Serial.println(buf);
  }
  return ( buf );
}

_
```*_

Not sure why the special template is necessary. Polymorphism allows you to set a base class pointer variable equal to a derived class pointer:

class BaseClass {
};

class DerivedClass : public BaseClass {
};

BaseClass *ptr[10];

void setup() {
  for (uint8_t i=0; i<10; i++) {
    ptr[i] = new DerivedClass;
  }
}

void loop() {}

gfvalvo:
Not sure why the special template is necessary.

RAII
It prevents memory leaks. Raw owning pointers are heavily discouraged in modern C++.

If your objects are going to live forever, you can get away with using raw pointers.
If you are going to be replacing the elements in your array, use smart pointers.

#include <memory>
#include <utility>

// C++11 doesn't support make_unique yet, so add it manually
template<typename T, typename... Args> std::unique_ptr<T> make_unique(Args&&... args) {
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

std::unique_ptr<GPIO_NONE> IOs[6];

void setup() {
  for (auto &io : IOs)
    io = make_unique<GPIO_INOUT>(...);
  [...]
  IOs[i]->setPin(PINS[i],i);
}

PieterP:
RAII
It prevents memory leaks. Raw owning pointers are heavily discouraged in modern C++.

Sounds like CS Professor Mumbo-Jumbo. Back in the day, men were men and we all used pointers :smiling_imp:

gfvalvo:
Sounds like CS Professor Mumbo-Jumbo. Back in the day, men were men and we all used pointers :smiling_imp:

Men were men and everyone struggled with memory leaks :smiley:

On a more serious note, it's a great solution to all kinds of problems. Because C++ always calls the constructor when an object goes out of scope (even when an exception occurs), you can be sure that all of your resources are cleaned up. This includes heap memory, but also files that must be closed, for example.

Python has a similar thing, using the with statement, and other languages like Java need special finally blocks to clean up resources, but I think the C++ approach is much easier.

I see your point. But, all the "std::'s" and the template "<<>>>'ing" sure makes for some ugly looking code. Nothing like the sparse elegance of 'C'.

Thank you PieterP and Gfvalvo,

You are helped the lot!
Currently Gfvalvo solution is enough since after creation of the object would be not reconfigured.

thanks again