Callback style

This is a discussion on Callback style within the C++ Programming forums, part of the General Programming Boards category; We have an expression language for one of our applications, that has variables. The expression is parsed once, a tree ...

  1. #1
    the hat of redundancy hat nvoigt's Avatar
    Join Date
    Aug 2001
    Location
    Hannover, Germany
    Posts
    3,139

    Callback style

    We have an expression language for one of our applications, that has variables. The expression is parsed once, a tree is built and it can be evaluated many times while the variables in the expression change. Variables have a callback, so the evaluator can request the current value.

    Code:
    namespace ExpressionLanguage
    	{
    		class CSAZVariable : public CSAZNamedType
    		{
    		public:
    			typedef CSAZValue (*GET_VALUE_CALLBACK)( const CSAZVariable&, void* );
    
    		private:
    			GET_VALUE_CALLBACK m_CallBack;
    			void* m_pUserData;
    			
    		public:
    			CSAZValue GetValue() const;
    			CSAZVariable( const wstring& name, ESAZDataType datatype, GET_VALUE_CALLBACK callback, void* userdata = NULL );
    		};
    	}
    As you can see, it's a simple function pointer with userdata, so basically everybody can have a static function, cast "userdata" to whatever neccessary and read the current value from there. It works fine, however it strikes me as somewhat old fashioned. More "C with classes" than C++.

    Can anyone point me to a more "C++"y way of doing callbacks or post a small example? Preferably without the use of Boost, just plain STL.
    hth
    -nv

    She was so Blonde, she spent 20 minutes looking at the orange juice can because it said "Concentrate."

    When in doubt, read the FAQ.
    Then ask a smart question.

  2. #2
    Registered User whiteflags's Avatar
    Join Date
    Apr 2006
    Location
    United States
    Posts
    7,627
    I don't think that there's an amazing difference between function pointers and functors (the objects that overload operator() to do something). You could do that, but it would require some template magic if you want both pointers and functors to work.

  3. #3
    Registered User
    Join Date
    Aug 2010
    Location
    Poland
    Posts
    681
    It think every callback will look old-fashioned and "C"-y. Maybe try to embed CSAZValue into userdata somehow - a nasty struct with a pointer, or, if you want more "C++"y way - a small class "CSAZUserValue" with virtual Get() method, something like this:

    Code:
    namespace ExpressionLanguage
    	{
    		class CSAZVariable : public CSAZNamedType
    		{
    		private:
    			CSAZUserValue* m_pUserData;
    			
    		public:
    			CSAZValue GetValue() const; // return m_pUserData->Get()
    			CSAZVariable( const wstring& name, ESAZDataType datatype, CSAZUserValue* userdata = NULL );
    		};
    	}
    Just giving out an idea (I guess you want to avoid templates and inheriting from CSAZValue class).

    EDIT:

    You could remove the DataType parameter now as well - dynamicly-typed expressions ahead.
    Last edited by kmdv; 08-17-2011 at 03:43 AM.
    I never put signature, but I decided to make an exception.

  4. #4
    Registered User
    Join Date
    Aug 2003
    Posts
    127
    You can give an interface.

    Code:
    class CSAZVariable : public CSAZNamedType
    {
    public:
    	class var_interface
    	{
    	public:
    		virtual ~var_interface();
    		virtual void get_value(const CSAZVariable&, void* userdata) = 0;
    	};
    
    private:
    	var_interface* m_CallBack;
    	void* m_pUserData;
    public:
    	CSAZValue GetValue() const
    	{
    		if(m_CallBack)
    			m_CallBack->get_value(*this, m_pUserData);
    		return XXX;	
    	}
    	CSAZVariable( const wstring& name, ESAZDataType datatype, var_interface* callback, void* userdata = NULL );
    };
    Or, std::function in C++11
    Last edited by jinhao; 08-17-2011 at 07:47 AM.
    Nana C++ Library is a GUI framework that designed to be C++ style, cross-platform and easy-to-use.

  5. #5
    Registered User gardhr's Avatar
    Join Date
    Apr 2011
    Posts
    151
    Quote Originally Posted by nvoigt View Post
    We have an expression language for one of our applications, that has variables. The expression is parsed once, a tree is built and it can be evaluated many times while the variables in the expression change. Variables have a callback, so the evaluator can request the current value.

    Code:
    namespace ExpressionLanguage
    	{
    		class CSAZVariable : public CSAZNamedType
    		{
    		public:
    			typedef CSAZValue (*GET_VALUE_CALLBACK)( const CSAZVariable&, void* );
    
    		private:
    			GET_VALUE_CALLBACK m_CallBack;
    			void* m_pUserData;
    			
    		public:
    			CSAZValue GetValue() const;
    			CSAZVariable( const wstring& name, ESAZDataType datatype, GET_VALUE_CALLBACK callback, void* userdata = NULL );
    		};
    	}
    As you can see, it's a simple function pointer with userdata, so basically everybody can have a static function, cast "userdata" to whatever neccessary and read the current value from there. It works fine, however it strikes me as somewhat old fashioned. More "C with classes" than C++.

    Can anyone point me to a more "C++"y way of doing callbacks or post a small example? Preferably without the use of Boost, just plain STL.
    The basic idea of building generic callbacks (functors) is simply to link templates to the rest of your code using virtual inheritance. Unfortunately, implementing such a thing properly requires a good smart pointer - something the STL sorely lacks (even C++0X's shared_ptr is only a slightly better choice). So...I would recommend using a well-tested functor library, personally. Sure, Boost is a pain in the ass to install, but in the long run you'd be saving yourself a lot of trouble! Anyway, in case you're still curious:

    Code:
    #include <memory>
    
    struct CSAZValue
    {};	
    	
    struct CSAZNamedType
    {};	
    	
    class CSAZVariable : public CSAZNamedType
    {
    public:	
    	template <typename Callback>		
    	CSAZVariable(Callback callback)
    	: m_CallBack(new CSAZFunctor<Callback>(callback))
    	{}
    		
    	CSAZValue Get()
    	{
    		return m_CallBack->Get(*this);
    	}
    private:			
    	struct CSAZFunctorBase
    	{
    		virtual CSAZValue Get(const CSAZVariable&) = 0;
    	};
    	
    	template <typename Callback>
    	struct CSAZFunctor : CSAZFunctorBase
    	{
    		CSAZFunctor(Callback callback)
    		: m_CallBack(callback)
    		{}
    		virtual CSAZValue Get(const CSAZVariable& csa)
    		{
    			return m_CallBack(csa);
    		}				
    		Callback m_CallBack;
    	};
    #ifdef USING_CPP0X
    private:
    	std::shared_ptr<CSAZFunctorBase> m_CallBack;	
    #else
    // ...yup, std::auto_ptr plain sucks!	
    public:
    	CSAZVariable(const CSAZVariable& goner)
    	: m_CallBack(const_cast<CSAZVariable&>(goner).m_CallBack)
    	{}	
    private:	
    	std::auto_ptr<CSAZFunctorBase> m_CallBack;	
    #endif	
    };
    The rest is pretty straightforward. You may have to write an adapter here or there to attend to those special cases, but for the most part nothing too complicated.

    Code:
    #include <iostream>
    struct c_style_callback
    {
    	typedef CSAZValue (*GET_VALUE_CALLBACK)(const CSAZVariable&, void*);
    	c_style_callback(GET_VALUE_CALLBACK callback, void* userdata)
    	: m_CallBack(callback), m_UserData(userdata)
    	{}
    		
    	CSAZValue operator ()(const CSAZVariable& csa)
    	{
    		std::cout << "c_style_callback::operator(const CSAZVariable&)" << std::endl;
    		return m_CallBack(csa, m_UserData);		
    	}
    	
    	GET_VALUE_CALLBACK m_CallBack;
    	void* m_UserData;	
    };
    
    CSAZValue global_get(const CSAZVariable&)
    {
    	std::cout << "global_get(const CSAZVariable&)" << std::endl;
    	return CSAZValue();
    }
    
    struct functor_get
    {
    	CSAZValue operator ()(const CSAZVariable&)
    	{
    		std::cout << "functor_get::operator(const CSAZVariable&)" << std::endl;
    		return CSAZValue();		
    	}
    };	
    
    CSAZValue static_get(const CSAZVariable&, void*)
    {
    	std::cout << "static_get(const CSAZVariable&, void*)" << std::endl;
    	return CSAZValue();	
    }
    
    int main(void)
    {
    	CSAZVariable g = CSAZVariable(global_get);
    	CSAZVariable f = CSAZVariable(functor_get());
    	CSAZVariable s = CSAZVariable(c_style_callback(static_get, NULL));
    	g.Get();
    	f.Get();
    	s.Get();
    }

  6. #6
    the hat of redundancy hat nvoigt's Avatar
    Join Date
    Aug 2001
    Location
    Hannover, Germany
    Posts
    3,139
    Great answers, thanks. I'll try some alternatives next week.
    hth
    -nv

    She was so Blonde, she spent 20 minutes looking at the orange juice can because it said "Concentrate."

    When in doubt, read the FAQ.
    Then ask a smart question.

  7. #7
    Super Moderator VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,590
    You might want to try the observer listener pattern. I use it frequently when I want to callback or notify an object of an event.

    Code:
    class ISomeListener
    {
       public:
          virtual ~ISomeListener() { }
          virtual void NotifyFoo() = 0;
    };
    
    class SomeListener : public ISomeListener
    {
       public:
          ...
          virtual void NotifyFoo()
          {
              //Respond to Foo notification
              std::cout << "Foo notification received" << std::endl;
          }
    };
    
    class SomeClassToListenTo
    {
        public:
           SomeClassToListenTo() : m_pListener(0)
           {
           }
           ...
           ...
           virtual bool AttachListener(ISomeListener *pListener);
           {
                bool result = false;
                if (m_pListener == 0 && pListener)
                {
                    m_pListener = pListener;
                    result = true;
                }
    
                return result;
           }
    
            virtual bool DetachListener(ISomeListener *pListener)
            {
                  bool result = false;
                  if (m_pListener == pListener)
                  {  
                       m_pListener = 0;
                       result = true;
                  }
    
                  return result;
            }
    
            void NotifyListener()
            {
                if (m_pListener)
                {
                     m_pListener->NotifyFoo();
                     std::cout << "Foo notification sent" << std::endl;
                }
            }
    
         private:
            ISomeListener *m_pListener;
     };
    
    
    SomeClassToListenTo *pListenTo = new SomeClassToListenTo();
    SomeListener *pListener = new SomeListener();
    
    if (pListenTo->AttachListener(pListener))
    {
        pListenTo->NotifyListener();
    }
    ...
    ...
    Last edited by VirtualAce; 08-19-2011 at 05:16 PM.

  8. #8
    Registered User gardhr's Avatar
    Join Date
    Apr 2011
    Posts
    151
    Quote Originally Posted by VirtualAce View Post
    You might want to try the observer listener pattern. I use it frequently when I want to callback or notify an object of an event.

    Code:
    class ISomeListener
    {
       public:
          virtual ~ISomeListener() { }
          virtual void NotifyFoo() = 0;
    };
    
    class SomeListener : public ISomeListener
    {
       public:
          ...
          virtual void NotifyFoo()
          {
              //Respond to Foo notification
              std::cout << "Foo notification received" << std::endl;
          }
    };
    
    class SomeClassToListenTo
    {
        public:
           SomeClassToListenTo() : m_pListener(0)
           {
           }
           ...
           ...
           virtual bool AttachListener(ISomeListener *pListener);
           {
                bool result = false;
                if (m_pListener == 0 && pListener)
                {
                    m_pListener = pListener;
                    result = true;
                }
    
                return result;
           }
    
            virtual bool DetachListener(ISomeListener *pListener)
            {
                  bool result = false;
                  if (m_pListener == pListener)
                  {  
                       m_pListener = 0;
                       result = true;
                  }
    
                  return result;
            }
    
            void NotifyListener()
            {
                if (m_pListener)
                {
                     m_pListener->NotifyFoo();
                     std::cout << "Foo notification sent" << std::endl;
                }
            }
    
         private:
            ISomeListener *m_pListener;
     };
    
    
    SomeClassToListenTo *pListenTo = new SomeClassToListenTo();
    SomeListener *pListener = new SomeListener();
    
    if (pListenTo->AttachListener(pListener))
    {
        pListenTo->NotifyListener();
    }
    ...
    ...
    The observer/listener pattern probably isn't appropriate here. Evaluating an expression is inherently sequential, so unless there are some really unusual design requirements going on it'd most likely be a utilization mismatch. And regarding that particular pattern...instead of tying the interface to a virtual base class, why not just use a functor? Better yet, a linked list of them so that you can attach multiple objects to a given observer? Just a thought.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Callback functions
    By WebmasterMattD in forum C++ Programming
    Replies: 3
    Last Post: 11-07-2003, 06:45 PM
  2. help!! callback in dll....
    By kimcs in forum Windows Programming
    Replies: 4
    Last Post: 06-30-2003, 07:47 AM
  3. reinterpret_cast, C-style cast or function-style cast
    By blight2c in forum C++ Programming
    Replies: 3
    Last Post: 05-14-2002, 10:07 PM
  4. c-style string vs. c++-style strings
    By Mbrio in forum C++ Programming
    Replies: 3
    Last Post: 02-10-2002, 11:26 AM
  5. how do I extract a style from a DWORD style
    By zMan in forum Windows Programming
    Replies: 1
    Last Post: 01-17-2002, 09:09 AM

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