One Class to rule them all?

I recently became aware of a function called forward_list which is not compatible with all Arduino platforms. But it's a very useful function that you can include in a library for example so that every time the library is instantiated, each instantiation gets added to a list where you can then act on all instantiations with simple code without needing to address each instantiation individually.

I wrote a library called BlockNot that lets you create easy to use, non-blocking timers, and someone created a pull request to add the capability of resetting all timers in one easy line of code and he used forward_list to accomplish this, but since it won't work on all platforms, I cant add it... but that brings me to my question:

Would it be possible to instantiate a class - let's call it Manager - the first time my library is instantiated then have the subsequent instantiations of the library address that one instantiation of Manager?

Obviously, I'd like to re-create this capability where all instantiations can be acted upon with a single line of code as a function within the library, but I don't know how to do what I'm thinking needs to be done in order to accomplish that.

Are you taking about a Singleton ?

You need a base class that is a list. A linked list would work. Then use that as one of the bases of your timer class. You can have a global function that runs down the list and calls your method of choice on each timer.

-jim lee

I understand conceptually what you're saying, but I don't know how to actually do what you are saying...

When you say that I need a class that IS a list, do you mean that there is some way to declare a class as a LinkedList? In Java, I would use the extends word like this:

public class MyList extends LinkedList<Long>

but how is this done in C++?

Next, when you say "Then use that as one of the bases of your timer class" ... what do you mean by "bases"?

I Googled that ... and you might be onto something there ... I need to learn more about it and experiment with it ... but if you have a specific implementation in mind that would serve my purpose, I'd love to see it.

the Singleton design pattern for C++ is a way to restrict the instantiation of a class to one object. So possibly not what you are looking at.

if you want the class to somehow magically capture all instances and write class member functions that would act on the list of instances, here is a very crude example without using anything "exotic" (a static array) nor handling empty slots that will store the first maxCapacity instances of your DummyClass into an array the class knows about.

The class member function printAllInstances() will walk through the list for you

const byte maxCapacity = 10;

class DummyClass
{
  public:
    static uint8_t maxCount;
    static DummyClass* instances[maxCapacity];

    // constructor
    DummyClass(char v) : id(v) {
      index = maxCount++;
      Serial.print(F("Constructor for instance # "));
      Serial.print(id);
      Serial.print(F(" at index: "));
      Serial.println(index);
      if (index < maxCapacity) instances[index] = this;
    }

    ~DummyClass() { /// does not deal with reusing empty slots
      Serial.print(F("Destructor for : "));
      Serial.print(id);
      Serial.print(F(" at index: "));
      Serial.println(index);
      if (index < maxCapacity) instances[index] = NULL;
    }

    char identifier() {
      return id;
    }

    static uint8_t total() {
      return maxCount;
    }

    static void printAllInstances() {
      Serial.print(F("Known instances: "));
      for (byte i = 0; i < maxCount; i++) {
        if (instances[i] != NULL) Serial.write(instances[i]->identifier());
      }
      Serial.println('\n');
    }

  private:
    char id;
    byte index;
};

uint8_t DummyClass::maxCount = 0;
DummyClass* DummyClass::instances[maxCapacity];

void setup() {
  Serial.begin(115200);
  DummyClass instanceX('X');
  DummyClass::printAllInstances();

  DummyClass* instanceY = new DummyClass('Y');
  DummyClass::printAllInstances();

  DummyClass* instanceZ = new DummyClass('Z');
  DummyClass::printAllInstances();

  delete instanceY;
  DummyClass::printAllInstances();

  delete instanceZ;
  DummyClass::printAllInstances();

  // as we exit the function, instanceX will get deleted as it's a local variable, so you'll see that in the Serial monitor too
}

void loop() {}

if you run that code on your arduino, Serial Monitor (@ 115200 bauds) will show

Constructor for instance # X at index: 0
Known instances: X

Constructor for instance # Y at index: 1
Known instances: XY

Constructor for instance # Z at index: 2
Known instances: XYZ

Destructor for : Y at index: 1
Known instances: XZ

Destructor for : Z at index: 2
Known instances: X

Destructor for : X at index: 0

the last line "Destructor for : X at index: 0" is interesting as you can see instanceX being freed up upon exit of the setup() as it's a local variable

Hope this gives you some ideas. To handle this better of course you would need a way to maintain the list over new/delete cycles (in the constructor and destructor) and be able to reuse empty slots. There are C++ classes that do that well for you. The static array with a max size is a way to know at compile time your memory impact, so might be something to consider.

Not to be obtuse, but I think this is one area where Java shines over C... as there is a class in Java called ArrayList which you can instantiate with any class at all (custom ... whatever)

List<myClass> myList = new ArrayList<>();

Then you simply use .add() or .remove() to add or remove elements from the dynamic array. There is simply no easy way to create dynamic arrays in C ... probably because of the rigid way in which C works with memory addressing... but that's just a guess on my part.

And as to creating a class that is a single instance accessible globally in Java, you simply declare your methods within a class as public static methods and there is then no need to ever create an instance of the class, you can simply use the methods within the class globally.

as there is a class in Java called ArrayList which you can instantiate with any class at all

Nothing Java can shine about :slight_smile:
in C++ you would use a Vector for example; There are a bunch of container classes

the challenge is that you are on a small micro-controller, so you should be pretty careful on how you handle memory

creating a class that is a single instance accessible globally in Java, you simply declare your methods within a class as public static

In C or C++ not everything needs to be a class... Just write a function...

Yes, but you have to instantiate the class before you can use it ... and in this case, you have to utilize the Singleton method in order to create a class that can be instantiated only once and then used "globally" ... is ... MESSY ... and it requires the class to manage itself and it has to utilize statics in order to work ... which I'm ok with, I'm just having a problem making that work in a header library since the variable that holds the instantiation of itself ... can't seem to be declared static and also given the value of NULL only to then be assigned a value the first time the class is instantiated ... here is an example that I've been trying to make work:

#ifndef FORWARDLIST_FORWARDLIST_H
#define FORWARDLIST_FORWARDLIST_H

class ForwardList {

public:
    
    ForwardList GetInstance() {
        if (instance == NULL)
            instance = new ForwardList();
        return instance;
    };

    
private:
    static ForwardList instance = NULL;
};

#endif //FORWARDLIST_FORWARDLIST_H

I'm writing this in CLion so it tells me what can and can't be done ... and it says - first of all, that NULL or null or Null is complete garbage and it has no idea what to do with it ... it calls it an 'undeclared identifier' ... and it may not be a problem for avrDude to compile ... I don't know yet.

I don't get it. There is no class at all. it's a function.

try this code:

#ifndef FORWARDLIST_FORWARDLIST_H
#define FORWARDLIST_FORWARDLIST_H

class ForwardList {
  private:
    static ForwardList * instance;

  public:
    ForwardList() {
      Serial.println("creating instance");
    }

    static ForwardList* getInstance() {
      if (instance == nullptr) instance = new ForwardList;
      return instance;
    }
};

ForwardList * ForwardList::instance = nullptr;

#endif

void setup() {
  Serial.begin(115200);
  Serial.println(F("Request #1"));
  ForwardList* a = ForwardList::getInstance(); // first call will allocate instance

  Serial.println(F("Request #2"));
  ForwardList* b = ForwardList::getInstance(); // further calls will Not
  Serial.println(a == b ? F("a and b are the same") : F("Something weird is happening"));

  Serial.println(F("Request #3"));
  ForwardList* c = ForwardList::getInstance(); // further calls will Not
  Serial.println(a == c ? F("a and c are the same") : F("Something weird is happening"));
}

void loop() {}

but it's not a list... it's a singleton

You could indeed use a std::vector to eliminate the need for a maximum capacity, but dynamically growing a vector is not ideal on a microcontroller.

I'd suggest the following:

  1. Create a struct DoublyLinkable with a next and prev pointer.
  2. Create a DoublyLinkedList class to maintain a linked list of such DoublyLinkable nodes, with operations to insert or remove nodes from the list.
  3. Make your class inherit from DoublyLinkable.
  4. Add a static DoublyLinkedList to your class.
  5. Add each instance to the list in the constructor of your class, remove it from the list in the destructor.

This approach requires no dynamically allocated storage, just one or two extra pointers in each instance. If you don't have specific requirements on the order, you can use a singly linked list.

You can find an implementation here:

Example:

#include <Arduino_Helpers.h>
#include <AH/Containers/Updatable.hpp> // UpdatableCRTP
#include <AH/STL/memory>               // make_unique

struct DummyClass : UpdatableCRTP<DummyClass> {
  const char *name;
  DummyClass(const char *name) : name(name) {}
  // Some function that does something
  void print(const char *msg) { Serial << name << " says " << msg << endl; }
  // Call the “print” function for all active instances
  static void printAll(const char *msg) {
    applyToAll(&DummyClass::print, msg);
    Serial.println();
  }
  // Get the list of all active instances
  static DoublyLinkedList<DummyClass> &instances() { return updatables;  }
};

void setup() {
  Serial.begin(115200);
  while (!Serial);

  // Create two instances
  auto a = AH::make_unique<DummyClass>("A");
  auto b = AH::make_unique<DummyClass>("B");
  DummyClass::printAll("message 1");
  
  // Disable the first instance
  a->disable();
  DummyClass::printAll("message 2");
  
  // Enable the first instance
  a->enable();
  DummyClass::printAll("message 3");
  
  // Delete the first instance
  a.reset();
  DummyClass::printAll("message 4");
  
  { // Create a scoped local instance
    DummyClass c = "C";
    DummyClass::printAll("message 5");
  } // Local instance is deleted
  DummyClass::printAll("message 6");

  // Another instance
  DummyClass d = "D";

  // Iterate over all instances
  Serial.print("Active instances: [ ");
  for (DummyClass &inst : DummyClass::instances()) {
    Serial << inst.name << ", ";
  }
  Serial.println("]");
}

void loop() {}

Output:

A says message 1
B says message 1

B says message 2

B says message 3
A says message 3

B says message 4

B says message 5
C says message 5

B says message 6

Active instances: [ B, D, ]

That would be exactly the same as a public: static member function in C++. But like J-M-L said, it's often better to use free functions. Not everything has to be a class.

What makes you think that?

instance is not a pointer, it cannot be null. Either way, never use NULL, use nullptr.

This is a memory leak.

Unlike in Java, you almost never use new in C++. If you need dynamic allocations, use std::make_unique. If you just want to create an object, allocate it on the stack (which is the default).

If you want to correct your code, you can find many C++ singleton classes online, but I don't think it will solve your problem.

One thing at a time ... once I figure out how to properly create a Singleton, next step is to make it a list somehow ...

I'm not sure I get what you want to do. a singleton always return the same instance and there is only one. If you need to instantiate multiple objects, the singleton won't help.

did you have a look at the code in post #12?

It's not a good idea to use dynamic allocation when you have tiny RAM.
You can force it to a point but that doesn't make it a good idea.
Use of String on Arduino has led many beginners to make Help! Help! posts.

Making your own buffer space and managing that can be wads safer and faster.

However, you can get direct-address external RAM for a Mega2560.

Suppose you run objects that act on the value in a global variable regardless of state?
Set the group value and the group pulls their own chains.

Here, calReset() resets all the bloody timers. You'll need LC_baseTools to compile this.

// *********************************************************
// ************** The resetables.h part ********************
// *********************************************************


#ifndef resetables_h
#define resetables_h

#include "lists.h"


class resetable : public linkListObj {

   public: 
            resetable(void);
   virtual  ~resetable(void);

            void  hookup(void);
   virtual  void  doReset(void);

            bool  hookedIn;
};


class resetables : public linkList {

   public:
            resetables(void);
   virtual  ~resetables(void);

            void  callReset(void);
};

//extern   resetables  theResetters;      // Uncomment two lines out when this goes into a .h file.
//extern   void        callReset(void);   //

#endif



// *********************************************************
// ************** The resetables.cpp part *****************
// *********************************************************


//#include "resetables.h"      // Uncomment this  line out when this goes into a .h file.


// The global resetables object list.
resetables theResetters;


// Our call that goes into the .ino file to reset everything.
void callReset(void) { 
  theResetters.callReset(); 
}


// *******************************
// resetable, base object to give things the ability to reset.
// *******************************

resetable::resetable(void) { 
  hookedIn = false; 
}


// Before we die, we need to tell our master to let us go.
resetable::~resetable(void) { theResetters.unlinkObj(this); }


// You can't call this in the contstructor. Love to, but can't.
// So call this the first time you add data or turn your resetable on.
void resetable::hookup(void) {
   
  if (!hookedIn) {
    theResetters.addToTop(this);
    hookedIn = true;
  }
}


// doReset(). We don't do anything here, but whomever inherits us will.
void resetable::doReset(void) { }



// *******************************
// resetables, management for the list of resetables. 
// Is delared as a global.
// *******************************


resetables::resetables(void) : linkList() {  }


resetables::~resetables(void) { }


// Run down the list and call the doReset() method on each one.
void resetables::callReset(void) {

  resetable* trace;

  trace = (resetable*) theList;
  while(trace!=NULL) {
    trace->doReset();
    trace = (resetable*)trace->getNext();
  }
}



// *********************************************************
// *********************************************************
// *************** Create a resettable timer  **************
// *********************************************************
// *********************************************************


#include <timeObj.h>


class resetTimer :   public timeObj,
                     public resetable {
   public :
            resetTimer(float inMs=10,bool startNow=true);
   virtual  ~resetTimer(void);

   virtual  void  doReset(void);                             
};


resetTimer::resetTimer(float inMs,bool startNow)
   : timeObj(inMs,startNow),
   resetable() { }
   
resetTimer::~resetTimer(void) {  }

void resetTimer::doReset(void) {
   
   reset();
   Serial.println("Timer has been reset,");
}


// *********************************************************
// *********************************************************
// ***************** Create a .ino file ********************
// *********************************************************
// *********************************************************

resetTimer timerOne(1000);
resetTimer timerTwo(2000);
resetTimer timerThree(3000);
resetTimer timerFour(4000);

void setup(void) {

   Serial.begin(57600);
   timerOne.hookup();
   timerTwo.hookup();
   timerThree.hookup();
   timerFour.hookup();
}

void loop(void) {

   if (timerOne.ding()) Serial.println("Timer one has expired.");
   if (timerTwo.ding()) Serial.println("Timer two has expired.");
   if (timerThree.ding()) Serial.println("Timer three has expired.");
   if (timerFour.ding()) Serial.println("Timer four has expired.");
   if (Serial.available()) {
      if (Serial.read()=='x') {
         callReset();
      }
   }
}

Tested and it works. If you get this running and are wondering how it works you can ask here.

In a nutshell : You mix resetable into any base class you desire. This gives you a doReset() call that can be filled out to reset that class of object. There is an example in the code resetTimer that shows this. In setup() call the hookup() method to every object you want to be reset on your callReset() function.

-jim lee

Ok i see that - but what I understood from the initial ask was to be able to make one call operate on the list of ALL instances of a class.

Say you have class C and 10 instances of C , you want to call C::method() and something happens in all 10 instances.

Where does the Singleton play a role there ? Is it an external record list that is a friend of C where you register / unregister your instances? (Why not build that into the class directly then?)

How often do we tell people to describe the problem and not their solution?

2 bytes a link, has to run a special function to work.... wth is wrong with having the class objects work on group or individual state?

Dynamic allocation on MCU's... not IF but WHEN, just how far it got before the crash....

so long, turning tracking off