Search code examples
c++function-pointerssignals-slots

A simple signal for a button in C++


I've been looking at a few signal/slot implementations, and with no exception they were pretty complicated, some even relying on MOC and extra code generation, like those of Qt.

I realize there are concerns such as threat safety and whatnot, but for a simple, single threaded scenario, is there something wrong with going for a simple approach, something like:

typedef void (*fPtr)();

class GenericButton
{
public:
    GenericButton() : funcitonToCall(nullptr) {}
    void setTarget(fPtr target) {
        funcitonToCall = target;
    }

    void pressButton() {
        if (funcitonToCall) funcitonToCall();
    }

private:
    fPtr funcitonToCall;
};

void doSomething(){
    std::cout << "doing something..." << std::endl;
}

void doSomethingElse(){
    std::cout << "doing something else..." << std::endl;
}

int main(){
    GenericButton myButton;
    myButton.setTarget(doSomething);
    myButton.pressButton();
    myButton.setTarget(doSomethingElse);
    myButton.pressButton();
}

It is still possible to chain several other methods and pass data in the target void function. So why all the complexity for something as trivial as executing some code when a button gets clicked.


Solution

  • This is a perfectly sensible solution, but don't restrict yourself to just function pointers. Use std::function which allows you to bind things, call member functions on objects, use lambdas and still resort to a function pointer where it makes sense. Example:

    #include <iostream>
    #include <functional>
    
    using namespace std::placeholders;
    
    
    class GenericButton
    {
    public:
        typedef std::function<void()> fPtr;
        GenericButton() : funcitonToCall(nullptr) {}
        void setTarget(fPtr target) {
            funcitonToCall = target;
        }
    
        void pressButton() {
            if (funcitonToCall) funcitonToCall();
        }
    
    private:
        fPtr funcitonToCall;
    };
    
    struct foo {
        void doSomething() const {
            std::cout << "doing something in a foo..." << std::endl;
        }
    
        static void alternative(int i) {
            std::cout << "And another, i=" << i << "\n";
        }
    };
    
    void doSomethingElse() {
        std::cout << "doing something else..." << std::endl;
    }
    
    int main() {
        GenericButton myButton;
        foo f;
        myButton.setTarget(std::bind(&foo::doSomething, &f));
        myButton.pressButton();
        myButton.setTarget(doSomethingElse);
        myButton.pressButton();
        myButton.setTarget(std::bind(foo::alternative, 666));
        myButton.pressButton();
        myButton.setTarget([](){ std::cout << "Lambda!\n"; });
        myButton.pressButton();
    }
    

    There's almost always a better solution in C++ than function pointers.

    If you don't have std::function/std::bind there's always alternatives in boost that work and you can roll your own std::function alternative without too much work which would be worth doing if you want to make something like this.

    Most of the signal/slot mechanisms that are around date from a time when things like boost::bind was not a viable option. Those days are long gone and you can get something standard and more flexible for little more complexity than just a function pointer.