Functors as replacement for callback?

This is a discussion on Functors as replacement for callback? within the C++ Programming forums, part of the General Programming Boards category; I've read a few articles around the web (and some of the posts on this forum) about functors and using ...

  1. #1
    Professional Chef leeor_net's Avatar
    Join Date
    Apr 2004
    Location
    Charles Town, WV
    Posts
    147

    Functors as replacement for callback?

    I've read a few articles around the web (and some of the posts on this forum) about functors and using them as a better alternative to function pointers. But I'm having a little difficulty understanding how to put them to effective use. The understanding I have so far in terms of using a functor to replace a function call is to use a functor to call a function from the class whos function I want called:

    Code:
    // Mostly psuedo code, probably doesn't compile, this is just about my understanding,
    // not intended to be an example of actual code.
    
    class Foo
    {
    public:
        void functionToCall()
        {
            // Do Something
        }
    };
    
    class Functor
    {
    public:
        Functor(Foo &foo) : foo(foo)
        {}
    
        void operator() (void)
        {
            foo.functionToCall();
        }
    
    private:
        Foo &foo;
    };
    
    class Bar
    {
    public:
        void invoker(Functor functor)
        {
            functor();
        }
    };
    
    int main()
    {
        Foo myFoo;
        Functor myFunctor(myFoo);
    
        Bar myBar;
    
        myBar.invoker(myFunctor);
    
        return 0;
    }
    Is this how functors are intended to be used as a better alternative to callback functions? This seems a bit overkill but then it could just be that it's a very small (and simple) example. I was originally going to use function pointers but not only are those are a nightmare to deal with when it comes to C++ classes the syntax becomes hideous and hard to understand (e.g., error prone).
    Last edited by leeor_net; 12-21-2009 at 06:07 PM. Reason: fixed a couple of mistakes.
    - Leeor

  2. #2
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,699
    Truth is, it just depends on the situation. The key is to design things so that it doesn't matter which is used! That's where templates come in:

    Code:
    
    
    #include <string>
    #include <iostream>
    
    using namespace 
        std;
    
    template < typename Logger >
    class test
    {
        public:
    
        test( Logger log )
        : log( log )
        {    
            log( "test( Logger )" );    
        }
        
        test( test const& rhs )
        : log( rhs.log )
        {    
            log( "test( test const& )" );    
        }
        
        virtual ~test( void )
        {
            log( "~test( void )" );            
        }
    
        Logger
            log;
    };
    
    /*
        This is a 'generator'. It simply forces the compiler to deduce 
        the exact type, without us having specify it explicitly. 
    */
    template < typename Logger >
    test< Logger > make_test( Logger log )
    {
        return test< Logger >( log );
    }
    
    /*
        A plain vanilla logger function
    */
    void just_log( char const* msg )
    {
        cout << msg << endl;
    }
    
    /*
        A functor object logger
    */
    class custom_embellish_log
    {
        public:
        
        custom_embellish_log( string const& pre, string const& post )
        : pre( pre ), post( post )
        {    }
        
        void operator ( ) ( string const& msg )
        {
            cout << pre << msg << post << endl;
        }
        
        string
            pre, 
            post;
    };
    
    int main( void )
    {
        make_test( just_log ).log( "just_log" );
        make_test( custom_embellish_log( "<[ ", " ]>" ) ).log( "custom_embellish_log" );    
    }
    Most importantly, we have abstracted the actual type, meaning that it's completely generic and, hence, totally reusable. The second thing, and this may not be immediately obvious, is that the actual parameter to the logging mechanism can be any type of object whatsoever - it just has to be constructible from whatever type is passed to it (in this case, our functor chose an std::string, which can be constructed with a c-style string) - making it an extremely flexible idiom.

    Hth.

  3. #3
    Super Moderator VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,596
    Templates do work until you start working with DLLs and the like. As long as you know which side the template expansion is going to take place you are ok. But you cannot expose a template class with __declspec(dllexport) without getting a warning from the compiler. The way around it is to use a PIMPL or an interface class that does not expose the template but sometimes that is easier said than done.

    Functors provide a nice way to do callbacks albeit I find them a tad clunky and overkill myself. Most examples on the net for them appear to be completely trivial and unrealistically simple. I've been working on this very thing and it has given me more than my fair share of head-aches. There are solutions but I must leave it at that. I'm sure you can find alternate solutions all over the internet.
    Last edited by VirtualAce; 12-21-2009 at 10:07 PM.

  4. #4
    Professional Chef leeor_net's Avatar
    Join Date
    Apr 2004
    Location
    Charles Town, WV
    Posts
    147
    Thanks for your replies. The thing is, that logging example really doesn't help. In fact, it looks a lot like this article on experts exhange. Unfortunately, I'm still failing to grasp how to apply it to my particular case -- I'm sure this is going to be a *facepalm* moment the second I 'get it'. Additionally, yes, I totally get that templates will makes things a great deal easier. For the time being, I'd like to not think about templates.

    In this case I'm developing a very simple GUI (basically buttons although I'd like to extend it in the future for other basic tasks). I don't need the functionality (or headaches) of the common but far too large libraries like Qt, KDE, TK and GTK+. I also didn't want to use something like Guichan because it hijacks my application.

    Basically, the idea is that when the buttons clicked on (in addition to whatever other future functionality I add), they call an internal onClicked() function. The idea was to have that onClicked() function call an external function (generally from an external unrelated class) to handle whatever logic is associated with that button (say, Quit, for example). This seems fairly straight-forward with function pointers (e.g., something like typedef void (*funcPtr)(int, double) and yet I keep reading that 'function pointers are bad in C++ and should only be used for legacy code'. Yet, functors seem, to quote Bubba, kludgy and overkill for this kind of an application of them. More importantly, I still really don't understand how to translate my function pointer case like the following to functors.

    Code:
    class Button
    {
    public:
        Button(/*callback specification here*/) {}
    
    private:
        onClicked(/* callback of sorts here */) { //call callback }
    
        // Some pointer to a callback function
    };
    
    class Foo
    {
    public:
        // I know this doesn't work
        Foo() : myButton1(this->myClickCallback), myButton2(this->myClickCallback)
        {}
    
    private:
        Button myButton1;
        Button myButton2;
    
        void myClickCallback(Button &button) { // do something depending on what the button is }
    }
    Are there are any real benefits to using functors versus function pointers in this case? Again, to quote Bubba, most of the articles and threads (and even two books) explain either trivial cases which really don't demonstrate anything or cases which are not applicable (for example, creating unary and binary funtors to use with STL algorithms which, btw, is extremely useful).

    Thanks again for everybody's help.
    - Leeor

  5. #5
    Captain Crash brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,239
    Some random thoughts

    * The function which uses a functor must be a template. This may not matter, or it could be a total showstopper. For instance, public APIs are almost never templates and don't usually use functors.
    * Because the code of a functor is template-substituted, this means that if the functor is inline, the entire function-plus-functor may be optimized better than if the semantics were hidden inside an object -- sometimes the optimization can be huge.
    * The functor eliminates a virtual call, but this is not that important when compared to the huge gains from the previous point
    * Anything which would compile if dropped into the surrounding algorithm can be used as a functor. Opposed to a callback object, which must inherit a specific interface.

    GIven the above list it seems like functors are preferable to callbacks, but the requirement that the algorithm that uses the functor must be a template is something that has a lot of ramifications.

    EDIT: Your original example doesn't use templates, but I don't consider it a correct implementation of functors. As-is, only an object of type Functor could be used. This is the same as using no callback/functor at all. Instead you are using the term "Functor" as a fancy word for sticking some code in another class instead of writing it directly.
    Last edited by brewbuck; 12-22-2009 at 12:20 AM.
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

  6. #6
    Professional Chef leeor_net's Avatar
    Join Date
    Apr 2004
    Location
    Charles Town, WV
    Posts
    147
    Quote Originally Posted by brewbuck View Post
    EDIT: Your original example doesn't use templates, but I don't consider it a correct implementation of functors. As-is, only an object of type Functor could be used. This is the same as using no callback/functor at all. Instead you are using the term "Functor" as a fancy word for sticking some code in another class instead of writing it directly.
    I figured that I was misunderstanding the whole point of functors.

    From exploring this here and reading additional materials I think I'm going to go ahead and use function pointers. I'm more familiar with them anyway and, despite knowing the power of templates I still don't see how I can have a functor operate as a function pointer would or, perhaps a better way of stating it, how to properly build code around functors such that they achieve the same end result of a function pointer.
    - Leeor

  7. #7
    Super Moderator VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,596
    Basically, the idea is that when the buttons clicked on (in addition to whatever other future functionality I add), they call an internal onClicked() function. The idea was to have that onClicked() function call an external function (generally from an external unrelated class) to handle whatever logic is associated with that button (say, Quit, for example). This seems fairly straight-forward with function pointers (e.g., something like typedef void (*funcPtr)(int, double) and yet I keep reading that 'function pointers are bad in C++ and should only be used for legacy code'.
    Think about that approach for a second. Don't you think you are handling the function information at the wrong level? In other words abstract it out by one more level and you will have a system that works. You don't need function pointers per se....you need an object that can handle calls to an object of type T for any message M where T could be an interface which has a function that can handle all calls to the object. You can actually accomplish the entire system without using function pointers at all. Who better to resolve the final function call than the object itself? No casting required and no function pointers. If you try to resolve the function calls outside of the object then you have all sorts of nasty and slow casting. Take a step back and maybe even UML your code or design and you will see at least a few ways to overcome this issue without using function pointers.

  8. #8
    Professional Chef leeor_net's Avatar
    Join Date
    Apr 2004
    Location
    Charles Town, WV
    Posts
    147
    Well... the way that buttons are set up at this point... they derive from an object I call Broadcaster. Broadcasters, effectively, handle all the stuff surrounding event messages and effectively 'broadcast' events which is what the Button's listen for (clicks, in particular). When they receive certain messages (like a mouse_button_up) within their area they register that as a click and go on to call the onClicked() function which generally takes care of the rest.

    The object in particular that I'm thinking of (the callee) has no business being a broadcaster but it can certainly listen for events. That would very much get the desired results I think...
    - Leeor

  9. #9
    Captain Crash brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,239
    I think what Bubba is getting at is that callbacks in C++ are handled by abstract interfaces, not function pointers. Example:

    Code:
    class Action
    {
    public:
        virtual ~Action() {} // Standard for abstract bases
    
        virtual void Do( Widget &actor ) = 0;
    };
    Now, the various actions you want to associate with events are just subclasses of Action with specific implementations of the Do() function. For instance:

    Code:
    class QuitAction : public Action
    {
    public:
        virtual void Do( Widget & ) { QuitTheApp(); }
    };
    The widget (button in your example) might have a way of registering some action to execute when it is clicked:

    Code:
    class Button
    {
    ...
        void setAction( Action *action ) { myAction = action; }
    ...
    };
    ...
    Button *quitButton = new Button;
    quitButton->setAction( new QuitAction() );
    And the button's onClick() function would just dispatch to the action:

    Code:
    void Button::onClick( ... )
    {
        myAction->Do( *this );
    }
    The purpose of the *this parameter is just so the action can figure out which object triggered it. It might not be necessary depending on how you choose to implement the actions.

    I can't help but point out that these ideas have been rehashed and reimplemented for decades. I've felt the urge to reimplement it all myself, too, and I wouldn't discourage you from trying. Just be aware that your final design will probably be a mess, not for lack of trying.

    (I guess it goes without saying that my examples don't demonstrate proper resource management)
    Last edited by brewbuck; 12-22-2009 at 01:44 AM.
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

  10. #10
    Professional Chef leeor_net's Avatar
    Join Date
    Apr 2004
    Location
    Charles Town, WV
    Posts
    147
    Actually you said it best how I was reimagining the current setup. It's ... sort of half right. I have the event handlers in place but the distribution system is crap.

    What I'm thinking is actually pretty close to what you were just suggesting. I'm actually going to go back to the drawing board, so to speak, and plan it out again before I go much further with this. I knew that I would need to rebuild some of the underlying code anyway because frankly, it sucks. I think I just went of doing whatever seemed right for awhile until I ended up with this little bit of a disaster on my hands.

    I'm really not trying to reimplement a full-scale GUI though. Just buttons and checkboxes, for the most part.
    Last edited by leeor_net; 12-22-2009 at 02:00 AM.
    - Leeor

  11. #11
    Super Moderator VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,596
    brewbuck's example is exactly what I was getting at and I could not have typed out a better explanation myself. It's not the only way to solve the issue but it is one that is both effective and extendable which are two qualities that make the approach very attractive.

  12. #12
    Professional Chef leeor_net's Avatar
    Join Date
    Apr 2004
    Location
    Charles Town, WV
    Posts
    147
    Thanks again for your help. I always appreciate everybody's feedback!
    - Leeor

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. for_each and functors
    By KIBO in forum C++ Programming
    Replies: 2
    Last Post: 08-10-2009, 07:00 AM
  2. Delayed Replacement Sort
    By Cpro in forum C++ Programming
    Replies: 3
    Last Post: 04-12-2007, 03:36 PM
  3. string padding and replacement functions
    By George2 in forum Tech Board
    Replies: 4
    Last Post: 11-19-2006, 12:40 AM
  4. NeHe Glaux replacement??
    By Razorblade Kiss in forum Game Programming
    Replies: 4
    Last Post: 02-25-2006, 02:30 AM

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21