First library: checking if value of int changed - can i do it simpler?

Hi, i made my first library. It works correctly, but after adding it to my big project, i think it could be done better (I think my .ino code could look cleaner). I hope u could direct me in good direction.

I need to track if the values โ€‹โ€‹of the 20 integers have changed.

So, firstly I made simple function:

bool update (int value) {

  if ( previousIntValue != intValue) {
    previousIntValue = intValue;
    return true;
  }

  else {
    return false;
  }

  }

And then in my code i just called:

if (update(X) ) doSomething1;

But since I need to check value of multiple ints, I created class:

class Tasks {

  private:
    int previousIntValue;

  public:
    bool update (int intValue) {

      if (previousIntValue != intValue) {
        previousIntValue = intValue;
        return true;
      }
      else return false;
   }

};

Now my .ino file looks like this:

#include "Tasks.h"
Tasks task1;
Tasks task2;

void setup() {
  }

void loop() {
  if (task1.update(X) )   doSomething1();
  if (task2.update(Y) )   doSomething2();
}

I need to create a lot of 'task' and then keep track of them.
Is there any option to achieve something like i wrote below? :

#include "Tasks.h"
Tasks task;

void setup() {
  }

void loop() {
  if (task.timer(X) ) doSomething1();
  if (task.timer(Y) ) doSomething2();

}

// now it would return true with every call, because one time previousInt will be compared to X and second time to Y.

Is it possible in any way? If no, is there anything u would change?

PS: I wrote class as example, I think adding my code as liblary would make it unnecessary harder to read. I also didn't wrote voids 'doSomething' or wrote int declarations (full code works), i hope it's okay and everything is understandable for u.

Also sorry for my english.
Thanks in advance!

To keep a class flexible in a way that it is meant to be used is actually how you did it.
If you want to keep track of 20 things, then you need 20 objects. That is not ugly, that is how it is supposed to be.

If you put an array of 20 variables in a class and add a parameter for the index, then you only need a single object. But then you have limited yourself to maximum 20. There are libraries that do that, but there is no need for it for your code.

The libraries for buttons that do debouncing use a object for a button. Twenty buttons means twenty objects.

You can create an array of objects:

Tasks myTasks[20];

void setup()
{
}

void loop()
{
  for( int i=0; i<20; i++)
  {
    if( myTask[i].update()
      doSomething(i);   // <-- a single function for all with a parameter
  }
}

The creation of the myTasks array calls twenty times the constructor for each element.

Can you put a pointer to the function in the class as well ? You need extra code to put the specific function pointer in the objects. Search for "callback function pointer class c++" or something like that, there are a few ways to do that.
Do you know the range based for loop ?
The code in the loop could look like this:

void loop()
{
  for( auto &a: myTasks)
    a.update();
}

You can't get cleaner than that :smiley:

[UPDATE] Changed "auto a" to "auto &a", see reply by PieterP below.

1 Like

I would suggest you rename your 'update' function to 'wasUpdated'. That will yield better readable code.
Further I would go for the array of objects as suggested.

1 Like

Thanks, i didn't think that i could use arrays in place like that!

You probably want to grab the task by reference rather than making a copy:

for(auto &a : myTasks)
    a.update();
1 Like

mind explaining how that dark magic works, please?

Which part, the '&a' or the syntax of the 'for' loop?

If the former, it's a Reference. C++ References and Pointers are cousins. You can (kind of, sort of) think of a Reference as a pointer that has already been de-referenced for you. But, don't take that analogy too far.

They each have their own tricks. Sometimes either will work in an application, sometimes one or the other is required.

If you're referring to the syntax of the 'for' loop, it's a Range-Based For Loop

@gfvalvo it was the range based loop I was asking about. Seems interesting, though, from what I gather its only useful for arrays created in the scope you're working in (ie not a passed value)...since they decay to pointers. have I got that right?

also asking a little bit about "auto", not how it works, but if its use is discouraged in situations where the variable type is easily written? For example, t hat could have been for( Tasks &a: myTasks) right? Is any upside/downsize to use auto instead? Maybe just readability?

Yes, 'auto' takes the type of the outcome. That can also be the return type of a function.
Using the 'auto' in the for-loop avoids that I have to think about the type and let the compiler take care of that.

When writing a library with a lot of software layers, the bottom layer can return a specific type and all the layers above it can use 'auto'. That can be useful.

With the slightest extra code, the range based for-loop can no longer be used. For example when something has to be done with the first or the last element. I sometimes think that the range based for-loop is for 50% a gimmick and 50% useful.

1 Like

Yes, but you can get around it with some template trickery:

template <size_t N>
void printArray(uint8_t (&theArray)[N]) {
  Serial.printf("Array Size = %d\n", N);
  for (auto element : theArray) {
    Serial.println(element);
  }
}

void setup() {
  Serial.begin(115200);
  delay(1000);

  uint8_t array1[] = {0, 1, 2, 3, 4};
  printArray(array1);

  Serial.println();
  uint8_t array2[] = {5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
  printArray(array2);
}

void loop() {
}

Output:

Array Size = 5
0
1
2
3
4

Array Size = 11
5
6
7
8
9
10
11
12
13
14
15
1 Like

Moreover, if the type you're passing has a properly defined iterator ... you don't even need the template:

#include <vector>

using ByteVector = std::vector<uint8_t>;
void printArray(ByteVector &theVector);

void setup() {
  Serial.begin(115200);
  delay(1000);

  ByteVector vector1 = {0, 1, 2, 3, 4};
  printArray(vector1);
  Serial.println();
  ByteVector vector2 = {5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
  printArray(vector2);
}

void loop() {
}

void printArray(ByteVector &theVector) {
  Serial.printf("Vector Size = %d\n", theVector.size());
  for (auto element : theVector) {
    Serial.println(element);
  }
}

Output:

Vector Size = 5
0
1
2
3
4

Vector Size = 11
5
6
7
8
9
10
11
12
13
14
15

True, but a "traditional" for loop doesn't help you with that either. But ...

#include <vector>

using ByteVector = std::vector<uint8_t>;
void printFirstLast(ByteVector &theVector);

void setup() {
  Serial.begin(115200);
  delay(1000);

  ByteVector vector1 = {0, 1, 2, 3, 4};
  printFirstLast(vector1);
  Serial.println();
  ByteVector vector2 = {5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
  printFirstLast(vector2);
}

void loop() {
}

void printFirstLast(ByteVector &theVector) {
  Serial.printf("Last = %d\n", *theVector.begin());
  Serial.printf("Last = %d\n", *(theVector.end()-1));
}

Output:

Last = 0
Last = 4

Last = 5
Last = 15
2 Likes

@gfvalvo & @Koepel --thanks for the explanation. I need to do a deep dive into templates, I never learned that subject properly.

I see the usefulness in "auto" , I'm just wondering if one can overuse it...I suppose there are probably some guidelines for its use somewhere, I'll have a look.

I need to walk that statement back a little. When you use classes from the STL, the template trickery is still there. It's just hidden from you.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.