Static variables in class object

I need to use a static variable in a member function of an object. So far, so good. However, if I create 2 or more instances of the same object then the static keyword appears not to be honoured as the static variable is acting like a global and its value is being changed by all instances of the object.

An example :

class testClass
{
  private:
    char * _name;
    unsigned long period = 1000;

  public:
    testClass::testClass(char * name)
    {
      _name = name;
    }

    void testClass::begin()
    {
      Serial.print("beginning ");
      Serial.println(_name);
    }

    void testClass::functionA()
    {
      unsigned long currentTime = millis();
      static unsigned long prevRunTime = millis();
      if (currentTime - prevRunTime < period)
      {
        return;
      }
      Serial.print(currentTime);
      Serial.print("\trunning ");
      Serial.println(_name);
      prevRunTime = currentTime;
    }
};

testClass object0("object0");
testClass object1("object1");

void setup()
{
  Serial.begin(115200);
  object0.begin();
  object1.begin();
}

void loop()
{
  object0.functionA();
  object1.functionA();
}

My first thought was to declare prevRunTime as a private variable so that each instance of the object had its own copy, However, doing that causes an error ISO C++ forbids in-class initialization of non-const static member 'testClass::prevRunTime' but the variable cannot be const because its value needs to change

I assume that there is a way to do what I want but I can't see it, so what is the solution ?

Wouldn't a plain non-const member variable work?

It doesn't matter where you put it, a static member of a class will always be like a global variable of this class, it doesn't belong to objects of this class.

The solution is to use a member variable, like your period... I'm not aware of any other way.

And if period is always 1000, then it is a good candidate for being a static :slight_smile:

Just FYI, I suppose you got this error because you tried to add static unsigned long prevRunTime = 0; under private.

It can be solved two ways:

  • if your compiler allows it, by adding the inline keyword :inline static unsigned long prevRunTime = 0;

  • Or by removing the initialization (remove = 0) and then put this line after the class definition : unsigned long testClass::prevRunTime = 0;

Just make it a member variable of the class. Each instance will have its own copy which will persist as long as the instance exists.

@guix thanks for the suggestions
I tried both of those solutions

class testClass
{
  private:
    char * _name;
    unsigned long period = 2000;
    inline static unsigned long prevRunTime;// = millis();

  public:
    testClass::testClass(char * name)
    {
      _name = name;
    }

    void testClass::begin()
    {
      Serial.print("beginning ");
      Serial.println(_name);
    }

    void testClass::functionA()
    {
      unsigned long currentTime = millis();
      if (currentTime - prevRunTime < period)
      {
        return;
      }
      Serial.print("prevRunTime ");
      Serial.print(prevRunTime);
      Serial.print("\tcurrentTime ");
      Serial.print(currentTime);
      Serial.print("\trunning ");
      Serial.println(_name);
      prevRunTime = currentTime;
    }
};

//unsigned long testClass::prevRunTime = 0;

testClass object0("object0");
testClass object1("object1");

void setup()
{
  Serial.begin(115200);
  object0.begin();
  object1.begin();
}

void loop()
{
  object0.functionA();
  object1.functionA();
}

and

class testClass
{
  private:
    char * _name;
    unsigned long period = 2000;
    static unsigned long prevRunTime;// = millis();

  public:
    testClass::testClass(char * name)
    {
      _name = name;
    }

    void testClass::begin()
    {
      Serial.print("beginning ");
      Serial.println(_name);
    }

    void testClass::functionA()
    {
      unsigned long currentTime = millis();
      if (currentTime - prevRunTime < period)
      {
        return;
      }
      Serial.print("prevRunTime ");
      Serial.print(prevRunTime);
      Serial.print("\tcurrentTime ");
      Serial.print(currentTime);
      Serial.print("\trunning ");
      Serial.println(_name);
      prevRunTime = currentTime;
    }
};

unsigned long testClass::prevRunTime = 0;

testClass object0("object0");
testClass object1("object1");

void setup()
{
  Serial.begin(115200);
  object0.begin();
  object1.begin();
}

void loop()
{
  object0.functionA();
  object1.functionA();
}

In both cases only one instance runs each period but there is a high probability that I have done something silly

@johnwasser @Whandall thanks both, that works and must have done when I tried it before opening this topic but I thought that it hadn't because the order in which the objects report that have run the function is inconsistent.

I attributed this to interaction between the 2 member variables not being static but could not make them static. However, thinking about it, the inconsistency is caused by the fact that each instance may trigger first depending on how the timing works out

They are not solutions, I was just showing how to solve that error :slight_smile:

They certainly did that

prevRunTime Could just be initialized in the begin call if that’s bugging you

if you mean static in the sense that their lifetime is more than the function call, that's not so much relevant for an instance. Instance variables are preserved as long as the instance exists and in a class context, static usually means you define a class variable, that is a variable that is shared (as you discovered) amongst all the instances of that class.

➜ hence you only needed an instance variable ( member variable) and no static

your testClass code could have looked like this (I randomised the period and used an array of instances to make it easier to call the functions on each instance)

class testClass
{
  private:
    const char * _name;
    unsigned long period;
    unsigned long prevRunTime;

  public:
    testClass(const char * name) :  _name (name) {}

    void begin() {
      Serial.print("beginning "); Serial.println(_name);
      prevRunTime = millis();
      period = random(2000, 4001);
    }

    void functionA() {
      unsigned long currentTime = millis();
      if (currentTime - prevRunTime < period) return;
      Serial.print("prevRunTime ");
      Serial.print(prevRunTime);
      Serial.print("\tcurrentTime ");
      Serial.print(currentTime);
      Serial.print("\trunning ");
      Serial.println(_name);
      prevRunTime = currentTime;
    }
};

testClass objects[] = {"object0", "object1", "object2"};

void setup() {
  Serial.begin(115200);
  for (auto && o : objects) o.begin();
}

void loop() {
  for (auto && o : objects) o.functionA();
}

@J-M-L thanks - I will take a proper look at this tomorrow, but in the meantime I have got my real code that the example in this thread was taken from working as I want using a member variable as suggested in previous posts

that's the way to go indeed - same in my code - and feel free to ignore if you got this working (you might be interested in the way you create an array of instances and Range-based for loop syntax if you want the code to be more compact)

PS: also if you have the function code directly into the class declaration, you don't need to prefix the methods with testClass::, that's only needed if they are defined outside (like when you do a .h and .cpp)