Thread: Making a static function which iterates every class instance

  1. #1
    Registered User
    Join Date
    May 2019
    Posts
    2

    Making a static function which iterates every class instance

    I am programming a certain class, which can be instanced multiple times. Some functions of this class are intended to traverse every instance.

    In my particular case, I am doing a valves controller. Each valve takes a time to open, hence, I need to call a Valve.loop() function which would call a .loop function on each particular instance of Valve class.

    Another case that could exemplify what I want, would be a sprite class; where the call to a Sprite.draw() function would call the .draw function on each instance of Sprite.

    The objective is to be able to just call ClassName.staticFunction() instead of having to store the instances in array and then iterate each.

    As a side note, I am working on Arduino. I think, however that this is applicable in any other case.

    This is the code I am trying (I rewrote it from scratch to remove the irrelevant things)

    Valve.h
    Code:
    #ifndef VALVE_H
    #define VALVE_H
    
    #define valvesAmount 8
    
    class Valve{
        private:
        void runCallback();
        public:
        int id;
        static int count;
        void setCallback(void (*callback)(int id));
        unsigned long ends=0;
        int openControlPin;
        Valve(int _openControlPin);
        void checkState();
        void open();
        static Valve *valves [valvesAmount];
        static void loop();
    
    };
    int Valve::count=0;
    Valve::Valve(int _openControlPin){
        openControlPin= _openControlPin;
        id=count;
        Valve::valves[id]=this;
        count++;
    }
    void Valve::runCallback(){
        //I would run some callbacks
    }
    void Valve::loop(){
        for(int vn=0; vn<Valve::count; vn++){
            Valve *current=Valve::valves[vn];
            current->checkState();
        }
    }
    
    void Valve::checkState(){
        unsigned long now=millis();
        
        if(ends){
            if(now>ends){
                ends=0;
                runCallback();
            }
        }
    }
    
    void Valve::open(){
        unsigned long now=millis();
        ends=now+3000;
    }
    #endif
    problem_code.ino

    Code:
    #include "Valve.h"
    
    void setup(){
        new Valve(1);
        new Valve(2);
        new Valve(3);
        new Valve(4);
    }
    void loop(){
        Valve::loop();
    }
    In my mind, it should work. Instead, I get a (linker?) error saying:
    Code:
    [...] AppData\Local\Temp\ccWvHJ6n.ltrans0.ltrans.o: In function `Valve::Valve(int)':
    
    sketch/Valve.h:26: undefined reference to `Valve::valves'
    
    sketch/Valve.h:26: undefined reference to `Valve::valves'
    I have tried many things and sought online a lot, but I remain clueless.

    This is my first post. I have read the guidelines, and I hope it is well adjusted to the communities expectations. Thanks!

  2. #2
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Maybe I'm too pessimistic, but I don't think a static member function will help: it operates at class level, but here you want to operate at object level, but for all objects.

    Storing objects, or perhaps smart pointers to objects, in a container seems like the most viable solution.

    If you really want a static member function that would invoke a specific member function on all objects of the class when it is called, then maybe what you could do is have a create() static member function serve as a "constructor", with the actual constructor declared private. This create() static member function creates a shared_ptr, storing a weak_ptr in a static member container, and returns the shared_ptr for the "outside world" to use. When the foo static member function is called, it then loops over this static member container, locking the weak_ptrs into shared_ptrs: if the result is that the weak_ptr is still valid, it then calls the bar member function of the object via the shared_ptr obtained from the weak_ptr, otherwise it removes the weak_ptr from the container. I don't really like this solution as it could be unnecessarily expensive to deal with shared_ptrs if you don't actually need them, but if you really want this mechanism then it might work for you.

    EDIT:
    Running with your sprite draw mention, this is a rough proof of concept of what I had in mind:
    Code:
    #include <algorithm>
    #include <iostream>
    #include <ostream>
    #include <memory>
    #include <vector>
    
    class Sprite
    {
    public:
        static void drawAll(std::ostream& out)
        {
            clean_sprites();
    
            for (const auto& sprite : sprites)
            {
                auto shared_sprite = sprite.lock();
                if (shared_sprite)
                {
                    shared_sprite->draw(out);
                }
            }
        }
    
        virtual ~Sprite() = default;
        virtual void draw(std::ostream& out) const = 0;
    protected:
        Sprite() = default;
    
        static void add_sprite(std::weak_ptr<Sprite> sprite)
        {
            sprites.push_back(sprite);
        }
    private:
        static std::vector<std::weak_ptr<Sprite>> sprites;
    
        static void clean_sprites()
        {
            sprites.erase(
                std::remove_if(
                    sprites.begin(), sprites.end(),
                    [](const auto& sprite) { return sprite.expired(); }
                ),
                sprites.end()
            );
        }
    };
    
    std::vector<std::weak_ptr<Sprite>> Sprite::sprites;
    
    class CatSprite : public Sprite
    {
    public:
        static auto create()
        {
            auto sprite = std::shared_ptr<CatSprite>(new CatSprite());
            add_sprite(sprite);
            return sprite;
        }
    
        virtual void draw(std::ostream& out) const override
        {
            out << "meow\n";
        }
    private:
        CatSprite() = default;
    };
    
    class DogSprite : public Sprite
    {
    public:
        static auto create()
        {
            auto sprite = std::shared_ptr<DogSprite>(new DogSprite());
            add_sprite(sprite);
            return sprite;
        }
    
        virtual void draw(std::ostream& out) const override
        {
            out << "woof\n";
        }
    private:
        DogSprite() = default;
    };
    
    int main()
    {
        auto dog = DogSprite::create();
        auto cat = CatSprite::create();
        {
            auto puppy = DogSprite::create();
            Sprite::drawAll(std::cout);
        }
        std::cout << "---\n";
        Sprite::drawAll(std::cout);
    }
    As you can see, it looks a bit cumbersome: it's akin to an extended singleton pattern, but is fundamentally different since it doesn't provide a single point of access (and you may need to disable copy construction, perhaps replacing it with a clone member function). It may be simpler to just maintain an external container of smart pointers (in which case you might even just need unique_ptr instead of shared_ptr).
    Last edited by laserlight; 05-02-2019 at 07:06 AM.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  3. #3
    Registered User
    Join Date
    May 2019
    Posts
    2
    I see. Many thanks for your answer!
    At the end I opted for a less object-oriented approach. the idea was to make the code simpler, rather than more complex.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. c++ 11 - static variables by instance
    By joaquim in forum C++ Programming
    Replies: 15
    Last Post: 09-13-2014, 11:24 AM
  2. instance of a class disappearing after function ends
    By combatdave in forum C++ Programming
    Replies: 14
    Last Post: 10-20-2006, 08:59 AM
  3. Problem with making static text using my class
    By Dae in forum Windows Programming
    Replies: 4
    Last Post: 06-17-2005, 12:02 AM
  4. Class function that returns the instance it's called from
    By L Boksha in forum C++ Programming
    Replies: 4
    Last Post: 05-25-2002, 11:52 AM
  5. Replies: 2
    Last Post: 12-25-2001, 04:18 PM

Tags for this Thread