Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

Trigger Class for C++

4.80/5 (10 votes)
4 Jan 2015CPOL2 min read 27K  
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)