Click here to Skip to main content
Click here to Skip to main content

Dynamically assigned and stored action sequence with handlers of the returned value, using boost::bind

, 24 Feb 2011 CPOL
Rate this:
Please Sign up or sign in to vote.
Module to dynamically assign, store, and call with handlers different functional objects: functors, functions, member functions.

Introduction

I want to offer to you not a bad (as I think) solution for a particular problem. Suppose you must give the user of your program the ability to dynamically assign tasks that the program will do. Or you want to write a test module that can be programmed - for example, you want the ability to define any sequence of performing tasks in any order. You can, of course, write several classes with the same interface: write an abstract base class, inherit several different children, and create a list of base pointers on their objects to call the same virtual interface functions. But you must inherit them, and that means – you must implement a lot of possible things. It will be time consuming at the least. Let's think of this another way. In real life, if you want to repair or make something, you take all the tools that you need one by one by your hand and do what you want to. Why not create such a tool as our hand (with brain of course) that can envelop any particular existing tool and use it? I propose two classes that will envelop any functionality, and another one that can store any sequence of these envelopes. And now to the details. (To use the code given with this article, you must know how to install boost on your computer or set it in the project property additional include directory.)

Using the code

The envelope

//(DynamicActionSequence.cpp)
class ActionInterface
{
public:
virtual ~ActionInterface(){}
      virtual void DoAcion() = 0;
};
 
1.    template<typename Action>
2.    class ActionHolder : public ActionInterface
3.    {
4.          Action m_Action;
5.    public:
6.          ActionHolder(Action act) : m_Action(act)
7.          {
8.          }
9.          virtual void DoAcion()
10.         {
11.               m_Action();
12.         }
13.   };

The class ActionInterface defines the interface function that must be overridden in a child and be called. Its child ActionHolder uses two kinds of polymorphism:

  1. It is inherited from an interface – so the pointer on it can be stored together with other pointers on the possible children in one container to call the same DoAction();
  2. It is a template class - so it can be stored inside any function object, returned by boost::bind.

If you have already used boost::bind, you can skip this paragraph.

This tool can bind a function or functor with up to 9 parameters which can be passed, and returns a functor that can be called simply with empty brackets like this:

11.    m_Action();

Here is an excerpt from the boost documentation:

////////////////////
int f(int a, int b)
{
    return a + b;
}

int g(int a, int b, int c)
{
    return a + b + c;
}

bind(f, 1, 2) will produce a "nullary" function object that takes no arguments, and returns f(1, 2). Similarly, bind(g, 1, 2, 3)() is equivalent to g(1, 2, 3).

But any time boost::bind is called for a different source (function or functor), it returns a completely new type of functor. So to store an unknown type, the class ActionHolder and the member storage must be declared as template types:

4.          Action m_Action;

As a result, ActionHolder can store any functor and itself be stored in the container.

Adding an action to the container of actions

Let's see what this instruction does:

dash.AddActionToSequence(boost::bind<int>(A<int>(), 2,3));

A<int>() creates an object of the A<int> class. According to the declaration of this class:

template<typename T>
class A
{
public:
      T operator ()(T a, T b)
      {
            cout<<"Inside class A T operator ()(T a, T b)"<<endl;
            return a * b;
      }
};

It has an overloaded operator () that receives two parameters. To call it, we must pass two params:

A<int> a;
A(2,3);

boost::bind<int>(A<int>(), 2,3) creates a functor object that is passed as a param to the dash.AddActionToSequence() call. So it will be stored as m_Action (line 4 above) inside the newly created ActionHolder object (see the following definition of the DynamicActionSequenceHolder class and particularly the declaration and body of the AddActionToSequence template member function). Finally, it will be called as m_Action(); - without passing any parameters at all.

Key moment: The interface function DoAction calls m_Action() without passing parameters, so we must adjust all the stored functionality means (functions, objects) to call them in this interface instruction without any parameters too.

ActionSequenceHolder

DynamicActionSequenceHolder is self-explanatory:

class DynamicActionSequenceHolder
{
      vector<ActionInterface *>  m_ActionsArr;
public :
      //DynamicActionSequenceHolder(){}
      ~DynamicActionSequenceHolder()
      {           
            int iSize = (int)m_ActionsArr.size();
            for(int i = 0; i < iSize; ++i)
                  delete m_ActionsArr[i];

      }
      template<typename Action>
      void AddActionToSequence(Action act)
      {
            m_ActionsArr.push_back(new ActionHolder<Action>(act));
      }
      void ActionSequence()
      {
            int iSize = (int)m_ActionsArr.size();
            for(int i = 0; i < iSize; ++i)
                  m_ActionsArr[i]->DoAcion();
      }
};

Let's take a look at:

template<typename Action>
void AddActionToSequence(Action act)
{
      m_ActionsArr.push_back(new ActionHolder<Action>(act));
}

This template function uses the template’s runtime type deduction from the received parameters, that cannot be done in the template class constructor – so it’s role is the following: deduce the type of the functor object returned by boost::bind, and create an ActionHolder on that particular type.

Main simply creates an object of DynamicActionSequenceHolder, adds some functionality to the container (including the global function and member function), and calls it afterwards.

It’s up to you to create the module that gives the user the ability to dynamically choose or set a sequence for your program functionality. I simply put in Main several possible simple variants. Of course, in the bigger program, all AddActionToSequencee calls must be enveloped in a try block to catch std::bad_alloc.

In the context of this part of the article (file DynamicActionSequence.cpp), I suppose that each of the called functional objects does all its job without receiving or returning any parameters. If you want a more elaborate version that can work with parameters, returned from called functors, let's see the next program – based on the previous version.  

Version 2 - working with returned values

First of all, let's look at the problems that are raised and must be solved to make this functionality workable. Types, returned by functors, can be different, so the best way to store returned values – template storage. Potentially, ActionHolder can be a host for stored values, but these values may be required outside of the ActionHolder. So the main problems are:

  1. How to store void?
  2. DynamicActionSequenceHolder stores pointers on the base interface – how can it return different return types through the same virtual function?
  3. Handler for the returned value may or may not be passed at all. (By the way, the requirement for the handler is - it must have the same interface to be universally called – it must be a functor that receives one parameter.)

I decided that this can be solved using template specialization. But instead of a specialized ActionHolder, here is a specialized auxiliary struct RetValueStorage. It is responsible for the solution for the first two problems. If we want to return different types by the same way – using the same function, it must be done using some sort of union and storing identification of the stored type to be deduced after (like CDBVariant in MFC). I think that template specialization of the storage struct RetValueStorage for these values will work much faster than any runtime identification of stored types. The third problem is solved using another specialization of another auxiliary class, HandlerStub.

And now, let's see who is who in this version:

  • class ActionInterface – the interface for calling stored functors through a virtual function, storing its children - ActionHolder objects in one container correctly deleting the children, and for storing and returning the same pointer on the template child of the struct Base_Value_Storage.
  • struct Base_Value_Storage – interface for correctly deleting children and for storing the returned type’s ID.
  • struct RetValueStorage – template struct that stores the returned value of any type (except void) plus sets and stores the returned type ID (this is made through specialization on any type that must be stored. As a result – a void specialization doesn’t have a void member; the RetTypeSpecificator macro is used to declare them quickly).
  • enum RetType – just for convenience when working with different stored types.
  • RetTypeSpecificator – macros that help to declare specalizations of RetValueStorage.
  • class ActionHolder – stores the action, returned value, and handler for that value. The program has only one iteration of the handler call. The functor returned value and handler is called upon it.
  • CallFunctor() – member template function inside ActionHolder. Together with  HandlerStub (or the real handler), they solve the following problems: for convenience, the user may or may not put a pointer on the existing handle in the ActionHolder class. So if the user does not provide a real handler for the returned value - in the ActionHolder constructor, we passed in the dynamically created HandlerStub object. A handler exists in every ActionHolder object, even if the returned type of the functor is void. The handler is a template member and can receive in its functor a parameter of any returned type except void. So the default HandlerStub class is specialized. The same problem happens with invoking m_Action();. This functor must be called according to the type returned from the stored action. This call may have a returned value or not (if void is returned). As a result, inside the virtual function, we call the specialized CallFunctor<>() template member function. In its CallFunctor<void>() specification, it can simply call m_Action(). In all other instances of CallFunctor, the corresponding storage receives the value returned from the m_Action() call.
  • TypeDeductor – auxiliary class used to check the similarity between the stored type and the buffer type (the variable that is passed to the ExtractValue function as a reference) when extracting data. If the types are the same – overloaded operator == returns true.
  • ExtractValue – global template function used to extract data that is stored inside the ActionHolder object in the RetValueStorage object.
  • HandlerStub – real stub that is passed when the user doesn’t want to use any handler at all.
  • It must be specialized because it must call its functor with the parameter, which cannot be a void object! So, an instance specialized on void does not have any functor at all.

  • DynamicActionSequenceHolder – manager that handles all the work with a particular action sequence. Its AddActionToSequence member function has two overloaded versions – one for receiving the real passed handler, and one for actions passed without any handler at all. This is for the user's convenience.
  • DataClass, A, B, C, D, Func, IntHandler, DoubleHandler – Tools for testing this functionality.

To use this module for any required type, you must:

  1. Include in RetTypeSpecificator.hpp the file with the declaration of the newly stored type.
  2. Add to the RetType enum a new type (if it is not there already).
  3. Use the macro RetTypeSpecificator with the new type.
  4. Add to the ExtractValue function a new Case for your type.
  5. If needed, define the handler for your type.
  6. Add your action and handler to the DynamicActionSequenceHolder object.

This description is doubled using a //:ref_1 token (use it in the Find in files dialog). I use this trick in all my projects. It really saves a lot of time when adding new functionality or when looking for a bug.

The code was compiled on VS 2005(8.0).

Good luck!!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Andriy Padalka
Software Developer (Senior)
Ukraine Ukraine
Now I'm working as a C++ developer and trainer and supporting
all my previously written applications.

Comments and Discussions

 
GeneralMore you use Boost, simpler the solution is. Pinmembershu1-Mar-11 8:15 
GeneralRe: More you use Boost, simpler the solution is. PinmemberAndrew Padalka1-Mar-11 21:18 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.141015.1 | Last Updated 24 Feb 2011
Article Copyright 2011 by Andriy Padalka
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid