Thread: Callbacks

  1. #1
    Deprecated Dae's Avatar
    Join Date
    Oct 2004
    Location
    Canada
    Posts
    1,034

    Smile Callbacks

    Hai guys,

    This is going to look really convoluted, but please bare with me. I'm writing an app which requires A LOT of asynchronous events. Normally I handle these the generally accepted way: add a function to the class: handle_x_and_y or on_x_and_y. Before you know it you have a ton of functions, but it's fine. In JavaScript, or any other scripting/programming language with inline functors, this goes a lot smoother and feels a lot more intuitive. The reason I believe is mostly context (scoping). If the fifth function will only ever be called from within the forth function, and this is repeated a lot, the program flow can get a little confusing. Yes, in the future maybe a different function will want to call the fifth function, but this is besides the point. Most examples and source I've seen people will order the functions by the order in which they are called in a sequence (ie. resolve-> connect -> handshake -> read headers-> read content -> write response).

    Anyway, so there's your standard option. Then there's boost::lambda, which is a solution, but looks even more cryptic than the next solution. Apparently you can inline function code in C++0x, which looks okay but won't be in my projects for quite a while. In a thread I read someone arguing they have little point because you could just use anonymous structs, except their functions can't be accessed directly (no name), so someone suggested just typedef struct. Which works fine. Of course, why not just go "struct A {};" versus "typedef struct {} A;"?

    One of the problems encountered with event callbacks in ActionScript 2.0 was losing scope, so you couldn't access upper variables. The same seems to exist in that solution. So I would think at the very least "this" should be passed to the structs. Thus their solution, in an example situation, would look like this:

    Code:
    #include <iostream>
    
    template<typename SELF_TYPE>
    class callback_base
    {
    	public: typedef SELF_TYPE self_type;
    
    	public: callback_base(SELF_TYPE& self_) : self(self_) { }
    
    	//public: SELF_TYPE& GetSelf() { return self; }
    
    	protected: SELF_TYPE& self;
    };
    
    class SomeClass
    {
    	public: void Start()
    	{
    		std::cout << "SomeClass::Start" << std::endl;
    
    		struct Callback1 : callback_base<SomeClass> { Callback1(SomeClass& sc) : callback_base(sc) {} void Invoke(unsigned int a1, unsigned int a2)
    		{
    			std::cout << "Callback1::Invoke" << std::endl;
    
    			struct Callback2 : callback_base<SomeClass> { Callback2(SomeClass& sc) : callback_base(sc) {} void Invoke(const char* a1)
    			{
    				std::cout << "Callback2::Invoke" << std::endl;
    
    				struct Callback3 : callback_base<SomeClass> { Callback3(SomeClass& sc) : callback_base(sc) {} void Invoke(bool a1)
    				{
    					std::cout << "Callback3::Invoke" << std::endl;
    				} }; Callback3 cb3(self);
    
    				cb3.Invoke(true); //temp
    			} }; Callback2 cb2(self);
    
    			cb2.Invoke("some argument"); //temp
    
    			struct Callback4 : callback_base<SomeClass> { Callback4(SomeClass& sc) : callback_base(sc) {} void Invoke(int a1)
    			{
    				std::cout << "Callback4::Invoke" << std::endl;
    
    				self.End();
    			} }; Callback4 cb4(self);
    
    			cb4.Invoke(1); //temp
    		} }; Callback1 cb1(*this);
    
    		cb1.Invoke(2, 2); //temp
    	}
    
    	public: void End()
    	{
    		std::cout << "SomeClass::End" << std::endl;
    	}
    };
    
    int main()
    {
    	SomeClass someClass;
    	someClass.Start();
    
    	std::cin.get();
    }
    I think my question is obvious. There's so much "wrong c++" and "unsafe c++" out there. I guess "wrongness" is sometimes debatable, but is this at least safe and fine? Are there better solutions, other than putting all callbacks together? (yes, I know good naming helps, but other languages look so much better with this solution - minus the cluttered definitions - preprocessors could help).

    Sorry for the tl;dr; Thanks in advance.
    Warning: Have doubt in anything I post.

    GCC 4.5, Boost 1.40, Code::Blocks 8.02, Ubuntu 9.10 010001000110000101100101

  2. #2
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    Not that there's anything necessarily wrong with it, per se, but I honestly don't see why you would want to do things like that. It just seems overly complicated and the amount of work required doesn't justify (in my mind, at least) whatever benefits it may have.

    That aside, for the constructor base-class initializer you have:

    Code:
    : callback_base(sc)
    Some compilers will accept that, but the correct way to do it is to explicitly provide the template parameter, eg:

    Code:
    : callback_base<SomeClass>(sc)
    And the code formatting is pretty terrible - it's very difficult to follow the way it's structured. I'd recommend something along the lines of:

    Code:
    #include <iostream>
    
    template<typename SELF_TYPE>
    class callback_base
    {
    	public: typedef SELF_TYPE self_type;
    
    	public: callback_base(SELF_TYPE& self_) : self(self_) { }
    
    	//public: SELF_TYPE& GetSelf() { return self; }
    
    	protected: SELF_TYPE& self;
    };
    
    class SomeClass
    {
    	public: void Start()
    	{
    		std::cout << "SomeClass::Start" << std::endl;
    
    		struct Callback1 : callback_base<SomeClass> 
    		{ 
    			Callback1(SomeClass& sc) 
    			:callback_base<SomeClass>(sc) 
    			{	} 
    			
    			void Invoke(unsigned int a1, unsigned int a2)
    			{
    				std::cout << "Callback1::Invoke" << std::endl;
    
    				struct Callback2 : callback_base<SomeClass> 
    				{ 
    					Callback2(SomeClass& sc) 
    					: callback_base<SomeClass>(sc) 
    					{	} 
    					
    					void Invoke(const char* a1)
    					{
    						std::cout << "Callback2::Invoke" << std::endl;
    
    						struct Callback3 : callback_base<SomeClass> 
    						{ 
    							Callback3(SomeClass& sc) 
    							: callback_base<SomeClass>(sc) 
    							{	} 
    							
    							void Invoke(bool a1)
    							{
    								std::cout << "Callback3::Invoke" << std::endl;
    							} 
    						}; 
    						Callback3 cb3(self);
    						cb3.Invoke(true); //temp
    					} 
    				}; 
    				Callback2 cb2(self);
    				cb2.Invoke("some argument"); //temp
    				struct Callback4 : callback_base<SomeClass> 
    				{ 
    					Callback4(SomeClass& sc) 
    					: callback_base<SomeClass>(sc) 
    					{	} 
    					
    					void Invoke(int a1)
    					{
    						std::cout << "Callback4::Invoke" << std::endl;
    						self.End();
    					} 
    				}; 
    				Callback4 cb4(self);
    				cb4.Invoke(1); //temp
    			} 
    		}; 
    		Callback1 cb1(*this);
    		cb1.Invoke(2, 2); //temp
    	}
    
    	public: void End()
    	{
    		std::cout << "SomeClass::End" << std::endl;
    	}
    };
    
    int main()
    {
    	SomeClass someClass;
    	someClass.Start();
    	std::cin.get();
    }
    Code:
    #include <cmath>
    #include <complex>
    bool euler_flip(bool value)
    {
        return std::pow
        (
            std::complex<float>(std::exp(1.0)), 
            std::complex<float>(0, 1) 
            * std::complex<float>(std::atan(1.0)
            *(1 << (value + 2)))
        ).real() < 0;
    }

  3. #3
    Deprecated Dae's Avatar
    Join Date
    Oct 2004
    Location
    Canada
    Posts
    1,034
    Thanks Sebastiani,

    Fixed the constructor.

    The reason for the code formatting was because I didn't want 2 tabs in between the scopes (struct is taking up it's own tab unnecessarily) - causing the code to widen quicker.

    Made it a macro to solve the complex syntax and format issues:

    Code:
    CREATE_CALLBACK(Callback1, SomeClass, 
    {
    	std::cout << "Callback1::Invoke" << std::endl;
    
    	self.End();
    }, void) cb1(*this);
    
    cb1.Invoke(); //temp
    I'm not too happy with that being the final product, but I'm a huge fan of nested callbacks. Ever use jQuery? (or a less-elegant JavaScript library) I guess I could just wrap it up in a scripting language (Python or LUA or JavaScript) but I'm not up to that atm - but it'll be easier to convert if I ever do.

    There's no way to turn an std::string or const char* into a macro, right? I tried # and ##. Meaning..
    #define class_A(type_A) #type_A
    std::string d("class A { } a;");
    class_A(d);

    I'm still interested in reasons against doing this (besides 1. macros are evil, 2. other developers will dislike it). Thanks!
    Last edited by Dae; 07-30-2009 at 06:00 AM.
    Warning: Have doubt in anything I post.

    GCC 4.5, Boost 1.40, Code::Blocks 8.02, Ubuntu 9.10 010001000110000101100101

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Input via callbacks or functions
    By zacs7 in forum Game Programming
    Replies: 3
    Last Post: 06-02-2008, 05:12 PM
  2. Callbacks to member functions
    By prog-bman in forum C++ Programming
    Replies: 0
    Last Post: 01-19-2008, 02:48 AM
  3. declaring function pointer types for callbacks
    By Pea in forum C Programming
    Replies: 6
    Last Post: 01-06-2005, 09:46 PM
  4. function callbacks?
    By Kibble in forum C++ Programming
    Replies: 3
    Last Post: 11-14-2002, 05:14 PM
  5. Callbacks
    By pradeepkrao in forum C++ Programming
    Replies: 5
    Last Post: 09-13-2002, 10:30 PM