methods for an array of a class

If I define a class of fuzzy, with its various variables and methods, I can easily create an array of

fuzzy cats[]={3,4,7}

Dealing with the individual elements, such as cats[2] is straightforward.

Is there a way to deal with all of the members at once?

For example, suppose I want

fuzzy.pet

to apply public routine pet() to each element?

I'm thinking of a situation where I create an array as above by declaring some element for the constricted (such as which pin to use), but want a generalized situation to maintain the elements (perhaps they need to check status, or turn off at a time stored in private variable, or . . .).

Being able to call the method for an entire array would seem to be cleaner code than an explicit loop (as well as the issue of coding the size of the array to get the exit condition of the loop).

[and, yes, all these years later I'm still grumbling that HyperTalk didn't allow "send thisMessage to every field on this card", as I had to work around it then, and still do in the modern successor LiveCode . . .]

The particular instance that brought it up is a class for relays, for which I want to check all of the off times, or for which I would want a call to any element of the array to turn on that element to first turn off all (whether for current usage, or because they would open too many water valves to maintain pressure, or let multiple model trains out of the station, or . . .)

How good are you with C++. I can think of a way but it is going to be pretty deep and it definitely won't be cleaner than a simple for loop.

for(int i = 0; i < numberOfCats; i++){
      cats[i].pet();
}

That get's er done in just one swipe.

The alternative is:

Build a list of fuzzies. Each time the constructor is called and creates a new fuzzy it adds one to this list. This will require dynamic memory usage, so it definitely ain't gonna be clean. You'll have to malloc a spot for the array with one extra spot, fill it from the old array with memcpy, add the one new one, and then delete the old array off the heap. But then you can have a member function that walks that list and calls pet() on each one.

That is going to take significantly more lines of code.

BTW: What is it today with people with perfectly simple code complaining that it isn't "clean". This is the third or fourth thread today where someone has a one liner that for some odd reason they don't consider "clean" or "concise" and they want to use some complicated bit of code with ten times the number of lines to replace it. Don't normally see that and then today, BAM, it's all over.

When you can get it done in one line, or a for loop with one line like this, then that is the definition of clean. It's simple, self documenting, and doesn't take a bunch of room or thought to write. What's "unclean" about it? There are complaints you can make, but cleanliness isn't one of them. I just don't understand what you are seeing that you don't think is "clean".

Range-based for loops are preferred, because they automatically loop over all elements. If you use a separate variable for "numberOfCats", you might get inconsistencies when deleting a cat from the array, and forgetting to change the "numberOfCats".

fuzzy cats[] = {3, 4, 7};
for (auto &cat : cats)
    cat.pet();

Delta_G:
Build a list of fuzzies. Each time the constructor is called and creates a new fuzzy it adds one to this list. This will require dynamic memory usage, so it definitely ain't gonna be clean. You'll have to malloc a spot for the array with one extra spot, fill it from the old array with memcpy, add the one new one, and then delete the old array off the heap.

Not necessarily. You can add a next pointer to each instance:

class Cat {
    private:
        Cat *next;
        static Cat *head;

    public:
        Cat() {
            // prepend this to linked list
            this->next = head;
            head = this;
        }
        ~Cat() {
            // remove this from linked list
            for (Cat **p = &head; *p != nullptr; p = &(*p)->next)
                if (*p == this) {
                    *p = this->next;
                    break;
                }
        }

        void pet() { /* ... */ }

        static void petAll() {
            for (Cat *p = head; p != nullptr; p = p->next)
                p->pet();
        }
};

Cat *Cat::head = nullptr;

Cat cats[4]; 

void setup() {
    Cat::petAll();
}

I've been doing some contemplating before responding (and my navel is much better for it, thank you :roll_eyes: )

Delta_G:
How good are you with C++.

Programming in general, excellent. I really can't remember all of the languages I've learned and forgotten over the decades, but I can generally just leap back in. Heck, I've fixed project in languages I've never seen.

C and C++, however, just don't stick around for me. I love the scoping (and want it in my high level languages, but can't have it :frowning: ), but I need to relearn them every few years every time I need one.

I can think of a way but it is going to be pretty deep and it definitely won't be cleaner than a simple for loop.

for(int i = 0; i < numberOfCats; i++){

cats[i].pet();
}

That's what I'm currently doing. I've just got delusions of coding

cats.pet_all();

at that point, numberOfCats is integral to the object so that I can't wander off with numCats instead of numberOfCats, or all the other ways I've found to bork a simple loop in the past . . .

BTW: What is it today with people with perfectly simple code complaining that it isn't "clean".

In this was, trying to remove ways to miscode. The size of the herd just seems to be something that the herd should know and handle.

The object could also have knowledge of how to treat different similar objects (e.g., different types of temperature sensors that communicate differently), but I suppose that could go into the methods.

In my case, though, "clean" needs to give way to saving bytes of RAM, so it seems that it will be a simple loop.

I guess I try to go "all in" on objects when I try to use them :)[/quote]

It would be a nightmare if this existed:

cats.pet_all();

I would never dare use fuzzy in any project with more than one developer and indeed, even with one, you've introduced a risk that you'll shoot yourself in the foot.

What if I have my array of cats that need petting, but I also have a ferret that must never be touched because he bites? And my werewolf can only be petted when it isn't a full moon. Now my lovely working code can be broken in a heartbeat by some thoughtless programmer wanting to pet his cats.

But the .pet() method for a ferret or alligator would be different, wouldn't it?

That's the fun part of using objects. .print() on an LCD screen is quite different to Serial.print() but they both use the same class.

To allow a petall method,you would need to register every instance as it hit its constructor. Rather as was proposed with a linked list earlier.

I know that the intent was to restrict the method to an array of fuzzy objects but I don't see a way to accomplish that.

Assume for a moment that an int was an object in c++, an incrementAll method would be a disaster in this scenario.

MorganS:
That's the fun part of using objects. .print() on an LCD screen is quite different to Serial.print() but they both use the same class.

They both call the IDENTICAL function from print.

It is the virtual write() method that print calls that is different.

The linked list suggested by @PieterP makes it quite easy:

class Fuzzy {
  public:
    Fuzzy(const char *n, bool p) {
      strncpy(fuzzyName, n, nameSize);
      fuzzyName[nameSize - 1] = '\0';
      allowPetting = p;
      next = head;
      head = this;
    }

    Fuzzy() {
      allowPetting = false;
      strcpy(fuzzyName, "???");
      next = head;
      head = this;
    }

    ~Fuzzy() {
      Fuzzy **ptrPtr = &head;
      while (*ptrPtr) {
        if (*ptrPtr == this) {
          *ptrPtr = (*ptrPtr)->next;
          break;
        }
        ptrPtr = &((*ptrPtr)->next);
      }
    }

    bool pet() {
      Serial.print(fuzzyName);
      Serial.print(F(": "));
      if (allowPetting) {
        Serial.println(F("OK to Pet"));
      } else {
        Serial.println(F("No Petting Allowed"));
      }
      return allowPetting;
    }

    void setPetAllowed(bool p) {
      allowPetting = p;
    }

    void setName(const char *n) {
      strncpy(fuzzyName, n, nameSize);
      fuzzyName[nameSize - 1] = '\0';
    }

    static void petAll() {
      Serial.println("PETTING ALL:");
      Fuzzy *ptr = head;
      while (ptr) {
        ptr->pet();
        ptr = ptr->next;
      }
    }

  private:
    static const uint8_t nameSize = 20;
    static Fuzzy *head;
    bool allowPetting;
    char fuzzyName[nameSize];
    Fuzzy *next;
};

Fuzzy * Fuzzy::head = nullptr;

Fuzzy animalArray[] = {
  {"Puss", true},
  {"Boots", false},
  {"Killer", true},
  {"Snuggles", false}
};

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

  Fuzzy::petAll();
  Fuzzy *newFuzzy1 = new Fuzzy("Baby", true);
  Serial.println();
  Fuzzy::petAll();
  Fuzzy *newFuzzy2 = new Fuzzy("Snappy", false);
  Serial.println();
  Fuzzy::petAll();
  delete newFuzzy1;
  Serial.println();
  Fuzzy::petAll();
  delete newFuzzy2;
  Serial.println();
  Fuzzy::petAll();
}

void loop() {
}

wildbill:
What if I have my array of cats that need petting, but I also have a ferret that must never be touched because he bites?

I've been assuming that these methods would be for the array, and not for all members of the class or subclass . . . although I've contemplated an array of assorted mammals of different types . . .

The catch with all of these is that RAM usage is skyrocketing, by almost an order of magnitude . . .

dochawk:
The catch with all of these is that RAM usage is skyrocketing, by almost an order of magnitude . . .

What code did you try?
The linked list just adds a single static pointer (2 Bytes) and one pointer for each instance.

Note that gfalvo's code adds 20 Bytes to each instance to save the name, you don't need that.

Pieter

I think I'm starting with a bool for status, and a long each for turnoff and turnoff times (and the turnoff could probably be an int) [the pin would presumably be a const.

If we implement a pointer to the next one, and make a pointer array for mixing subtypes in the same array that's another four bytes each, so increasing b y about half half.

Hmm, not as explosive as I was thinking . . . but compared to a non-object version for my relays (a byte for which one is on, a long for when it went on, and an int for when to shut it off . . . (only one would be active at a time over water pressure on. the project that led to this) . . .

Unless you're facing memory issues, there's nothing wrong with using more memory if it makes the code easier to read. If you have 10 relays, that's just 22 bytes extra if you add them to a list (~1% of the available memory of an UNO). In most cases, that shouldn't be an issue.

Could you explain your specific project? Maybe we could propose another solution.

Just another suggestion:
Instead of using an array, why not create a wrapper object around the array? Call it FuzzyArray or whatever and implement the [] operator to allow access to the array. Then implement a function pet_all() which iterates over the array. Yes you are still iterating over it, but that's hidden in the wrapper.

// Create the wrapper
FuzzyArray fuzzyArray;
// setup array
// ...
// access elements as if fuzzyArray was an array
fuzzyArray[0].pet();
// pet all
fuzzyArray.pet_all();
// or even this (I wouldn't suggest this at is a bit ambigous to the reader):
fuzzyArray.pet();

Make a class pets. cats would inherit from pets. You can then later create class dog, if you like, which would also inherit from pets

I think this is quite an elegant solution if you have many methods to call:

Code:

---



```
template <class T, size_t N>
struct Array {
T data[N];
constexpr static size_t length = N;

// Subscript operators
    T &operator[](size_t index) { return data[index]; }
    const T &operator[](size_t index) const { return data[index]; }

// Iterators
    T *begincolor=#000000[/color] { return &data[0]; }
    const T *begincolor=#000000[/color] const { return &data[0]; }
    T *endcolor=#000000[/color] { return &data[N]; }
    const T *endcolor=#000000[/color] const { return &data[N]; }

// Applicators
    template <class... Sign, class... Args>
    void applyToAll(void color=#000000[/color]color=#000000[/color], Args... args) {
      for (T &t : *this)
        color=#000000[/color]color=#000000[/color];
    }
    template <class... Sign, class... Args>
    void applyToAll(void color=#000000[/color]color=#000000[/color] const, Args... args) const {
      for (const T &t : *this)
        color=#000000[/color]color=#000000[/color];
    }
};
```

|

You would use it like this:

template <class T, size_t N>
struct Array {
    T data[N];
    constexpr static size_t length = N;

    // Subscript operators
    T &operator[](size_t index) { return data[index]; }
    const T &operator[](size_t index) const { return data[index]; }

    // Iterators
    T *begin() { return &data[0]; }
    const T *begin() const { return &data[0]; }
    T *end() { return &data[N]; }
    const T *end() const { return &data[N]; }

    // Applicators
    template <class... Sign, class... Args>
    void applyToAll(void (T::*m)(Sign...), Args... args) {
      for (T &t : *this)
        (t.*m)(args...);
    }
    template <class... Sign, class... Args>
    void applyToAll(void (T::*m)(Sign...) const, Args... args) const {
      for (const T &t : *this)
        (t.*m)(args...);
    }
};

class Cat {
  private: 
    const char *name;
    
  public: 
    Cat(const char *name) : name(name) {}
    void pet() const {
      Serial.print("Petting ");
      Serial.println(name);
    }
    void feed(const char *food, unsigned int quantity) {
      while (quantity --> 0) {
        Serial.print("Feeding ");
        Serial.print(food);
        Serial.print(" to ");
        Serial.println(name);
      }
    }
};

void setup() {
  Serial.begin(115200);
  while(!Serial);
  Array<Cat, 5> array = {{
    "Tom", "Oscar", "Tiger", "Simba", "Nala"
  }};
  Serial.println();
  Serial.println("Petting all cats:");
  array.applyToAll(&Cat::pet);
  Serial.println();
  Serial.println("Feeding all cats:");
  array.applyToAll(&Cat::feed, "fish", 2);
  Serial.println();
  Serial.print("sizeof(Cat) = ");
  Serial.println(sizeof(Cat)); // 2 bytes, just the name pointer
}

void loop() {}

It doesn't add any overhead, everything is resolved at compile time.

If you just need to pet them, the variadic templates and member function pointers might be overkill, and you could just do:

template <size_t N>
struct CatArray{
  Cat cats[N];
  void petAll() {
    for (Cat &cat : cats)
      cat.pet();
  }
};

// ...

CatArray<5> catArray = {{
  "Tom", "Oscar", "Tiger", "Simba", "Nala"
}};
catArray.petAll();

@PieterP great example, this is exactly what I meant! Just a quick note on the iterator methods: In the arduino environment begin() and end() are kind of "reserved" for library initialization and deinitialization (see APIStyleGuide) so it might be ambigous for other users who don't know the source code. The range-based for loop can iterate over the data array just fine, no need for iterators.

template <class... Sign, class... Args>
    void applyToAll(void (T::*m)(Sign...), Args... args) {
      for (T &t : data)
        (t.*m)(args...);
    }

This is at least what I think, I'm not that familiar with templates. Please correct me if I'm wrong.

LightuC:
The range-based for loop can iterate over the data array just fine, no need for iterators.

For iterating inside of the Array class, you can indeed do that.
I think that having begin and end methods is very useful, since iterating over an array is something you do all the time.
I do agree that it might be confusing for new users, but it's definitely not insurmountable.

LightuC:

template <class... Sign, class... Args>

void applyToAll(void (T::*m)(Sign...), Args... args) {
      for (T &t : data)
        (t.*m)(args...);
    }




This is at least what I think, I'm not that familiar with templates. Please correct me if I'm wrong.

That looks alright to me :slight_smile:

PieterP:
For iterating inside of the Array class, you can indeed do that.
I think that having begin and end methods is very useful, since iterating over an array is something you do all the time. [...] but it's definitely not insurmountable.

I didn't think of that, shame on me. Well, it is a style guide anyway and being able to use a range-based loop directly on the Array instance seems like a pretty good reason to use iterators! Even if they (begin(), end()) are called by the user, they don't do anything anyways :smiley:

PieterP:
Could you explain your specific project? Maybe we could propose another solution.

For the current projects, they'll all certainly fit.

I do have a bit of an 8 bit micro hangover from the limitations in the 70s hardware I grew up on . . . I even have to force myself to use longer variable names, even though they haven't counted against limits in years!

Add that to a desire to keep things "clean", and I'm in constant conflict! :o

And there's a visceral satisfaction ton in getting hardware down to bare minimums, which has me thinking ahead to smaller MCUs, and . . .

Anyway, the one I'm working on right now (and need to have functional within a few days) is a sprinkler controller.

Early last summer yet another "high end" controller went out--and only partway out, so we noticed the first sprinkler circuit running, while other circuits were not!!! It cost me the front yard, the back yard, 3-5 fruit trees (we're still not sure what's coming back . . .), some new berry bushes and some grape vines. By the time we realized that we did have a problem it was too late . . .

Anyway, for the moment it will be a nano or mini connected to 7 relays (off a 16 relay board) pumping 24vac to 7 solenoids. Only one will be on at any given time to control usage of water pressure.

I ned to seed the lawn after our last cold snap this week (and fo the first time, there are feral cats hanging around my yard to deal with the air rats!).

The mcu for this will attach by BLE to a pi 0w hanging from my living room wall :roll_eyes: for the moment and actually report turning on and off (that pi will handle logging and control of a small army of garden mcus eventually. Also, I will switch to nrf transceivers of I can get a few more yards range).

Hopefully by the end of summer, I will have a current sensor around the output to see if current is actually flowing to the solenoids when it is supposed to be--and report the error if not! [we get 115F in the summer, and usually over 100F daytime high. We really can lose plants or lawn very quickly. I giggle when I read pages advocating once a week lawn watering; you can't keep it alive with only once a day here . . .]

Moving forward after that, I want to add pressure sensors to each pvc pipe (making sure the valve actually opened!).

Eventually, there will be solar/battery units with their own soil moisture sensors and valves for sections of garden with their own latching water solenoids.

The hothouse will use a handful of sensors for winter to turn on distributed smaller heaters or IR bulbs.

It will be nice to put as much as possible into a library, as so many of the monitoring/controlling things are similar.