Click here to Skip to main content
15,881,882 members
Articles / Programming Languages / C++
Tip/Trick

Trigger Class for C++

Rate me:
Please Sign up or sign in to vote.
4.80/5 (10 votes)
4 Jan 2015CPOL2 min read 26.2K   15   5
Template class for calling trigger functions by a key

Introduction

This simple class provides an interface for calling registered functions by a key. The key can be everything you want assumed that your key is comparable by std::map. A function is called with only one parameter but you can use a container class for multiple parameters.

The class uses three template parameters. The first TReturn specifies the trigger function return parameters. The second TParameter specifies the trigger function parameter. The third template argument is the default key and has a default type of a string because normally you want to call trigger functions by a string specifier.

The internal storage of a function is implemented with boost functions. Also, you can register a class member function as a function so you can simply inherit this class.

At the moment, there is one problem. If you want to raise a trigger function and this function was not registered, the return value is undefined. To solve this problem, you could easily implement a check function.

C++
template<typename TReturn, typename TParameter, typename TDefaultKey = std::string>
class Trigger {
public:
    typedef boost::function<TReturn (TParameter)> triggerFunction_T;
    typedef std::map<TDefaultKey, triggerFunction_T> triggerMap_T;
    typedef std::pair<TDefaultKey, triggerFunction_T> triggerPair_T;

    Trigger(){
        triggers.reset(new triggerMap_T);
    };

    virtual ~Trigger(){

    };

    /** Register a class member trigger function.
     *
     */
    template<typename TFunction, typename TObject>
    int addTriggerAcceptor(TDefaultKey name, TFunction f, TObject obj){
        typename triggerMap_T::iterator it;
        triggerFunction_T                    trigger = boost::bind(f,obj,_1);
        it = triggers->find(name);
        triggers->insert(triggerPair_T(name,trigger));
        return 0;
    };

    /** Register a trigger function.
     *
     */
    int addTriggerAcceptor(TDefaultKey name, triggerFunction_T f){
        typename triggerMap_T::iterator it;
        it = triggers->find(name);
        triggers->insert(triggerPair_T(name,f));
        return 0;
    };

    TReturn raiseTrigger(TDefaultKey name, TParameter param){
        typename triggerMap_T::iterator it;

        it = triggers->find(name);
        if(it != triggers->end()){
            return it->second(param);
        }
    };

private:
    boost::shared_ptr<triggerMap_T> triggers;
};

Second Version using C++11 Features

The second version of the trigger class makes use of C++11 features. The interface didn't change but the template arguments are not compatible. The first template argument TReturn is the return type of the trigger function. The second template parameter TKey is the type of the key to call a trigger function. There is no more default key so you have to specify the key all the time.

A disadvantage of the first version was the lack of more than one function argument. In the second version, I make use of a variadic template. TArgs specifies the function parameters. This makes it possible to use as many function arguments as you wish.

When you want to determine if there is a trigger with a given key, you can use the hasTrigger function. This function returns true if there is a registered trigger. Using this function will lookup the map. First using hasTrigger to check if you can call a trigger function and then calling it will result in a double lookup of the map. To prevent this, you can call the trigger and catch the exception thrown by raiseTrigger.

C++
template<typename TReturn, typename TKey, typename... TArgs>
class Trigger2 {
public:
    typedef std::function<TReturn (TArgs...)> triggerFunction_T;
    typedef std::unordered_map<TKey, triggerFunction_T> triggerMap_T;
    typedef std::pair<TKey, triggerFunction_T> triggerPair_T;

    Trigger2(){
        triggers.reset(new triggerMap_T);
    }

    virtual ~Trigger2(){

    };

    bool addTriggerAcceptor(TKey key, triggerFunction_T f){
        typename triggerMap_T::iterator it;
        it = triggers->find(key);
        if(it != triggers->end()){
            return false;
        }else{
            triggers->insert(triggerPair_T(key,f));
        }
        return true;
    }

    template<typename TFunction, typename TObject>
    bool addTriggerAcceptor(TKey key, TFunction f, TObject obj){
        triggerFunction_T trigger = [f,obj](TArgs... args){
            return (obj->*f)(args...);
        };

        typename triggerMap_T::iterator it;
        it = triggers->find(key);
        if(it != triggers->end()){
            return false;
        }else{
            triggers->insert(triggerPair_T(key,trigger));
        }
        return true;
    };

    TReturn raiseTrigger(TKey key, TArgs... args){
        typename triggerMap_T::iterator it = triggers->find(key);
        if(it != triggers->end()){
            return it->second(args...);
        }else{
            /* Throw exception */
            throw std::bad_function_call();
        }
    }

    bool hasTrigger(TKey key){
        typename triggerMap_T::iterator it;
        it = triggers->find(key);
        if(it != triggers->end()){
            return true;
        }else{
            return false;
        }
    }
private:
    std::shared_ptr<triggerMap_T> triggers;
};

Using the Code

The new version interface didn't change a lot. You should only take care of the template parameters, which changed. The example below contains a function and a testing class with a function. The function takes two parameters (int, double).

C++
#include <iostream>
#include <string>
#include "Trigger.hpp" /* Include the trigger template here */

double testfunction(int a, double b){
    return a + b;
}

class Testclass {
public:
    int i;

    Testclass(){
        i = 5;
    }

    double testfunction(int a, double b){
        return a + b + i;
    }
};

int main(int argc, const char **argv){
    Testclass test;

    Trigger2<double,std::string,int,double> trigger;
    trigger.addTriggerAcceptor("test",&testfunction);
    trigger.addTriggerAcceptor("testclass",&Testclass::testfunction,&test);
    std::cout << trigger.raiseTrigger("testclass",5,10.15) << std::endl;
    std::cout << trigger.raiseTrigger("test",5,10.15) << std::endl;
}

History

  • 05.01.2015: Removed the use of reserved names although it compiles perfect on gcc, these names could break builds on other compilers (Thanks to .:floyd:.)
  • 08.08.2015: Second version has the trigger class using C++11 only.

License

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


Written By
Germany Germany
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionWhat happens when a functions is not registered? Pin
William Peets7-Jan-15 3:23
William Peets7-Jan-15 3:23 
AnswerRe: What happens when a functions is not registered? Pin
Geoffrey Mellar7-Jan-15 3:28
Geoffrey Mellar7-Jan-15 3:28 
GeneralUse C++11 instead of boost, pal Pin
bvbfan6-Jan-15 7:53
bvbfan6-Jan-15 7:53 
GeneralMy vote of 5 Pin
sampad_ali13706-Jan-15 6:29
sampad_ali13706-Jan-15 6:29 
GeneralUse of reserved symbols Pin
.:floyd:.5-Jan-15 3:13
.:floyd:.5-Jan-15 3:13 

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

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