5

一連の単項関数オブジェクトを順番に実行するメカニズムを設計しています。これらの関数オブジェクトは実行時に割り当てられますが、問題は、これらの関数オブジェクトのパラメーターの型が異なることです。

私がやりたいことは次のようなものです:

class command_sequence {
private:

/* some kind of container */

public:
    void add( FUNC_OBJ &func, PARAM val );
    void run(void);
};

class check_temperature {
public:
    void operator() (int celsius) {
        if(celsius > 26) {
            cooler.switch_on();
        }
    }
};

class log_usage {
public:
    void operator() (std::string username) {
        username.append(" logged in");
        syslog(LOG_NOTICE,username.c_str());
    }
};

command_sequence sequence;
log_usage logger;
check_temperature checker;

sequence.add(logger, std::string("administrator"));
sequence.add(checker, lobbyMeter.read_temperature());
sequence.add(logger, std::string("lecture"));
sequence.add(checker, classroomMeter.read_temperature());
sequence.run();

C コードを書いている場合、void* をパラメーターとして取るコールバック関数ポインターを使用するしかありません。しかし、私は現在 C++ を使用しています。それを処理するエレガントな方法があるはずです。

私が今考えることができる最善の方法は、抽象ラッパー クラスから仮想的に継承するテンプレート クラスを宣言することです。

class command_sequence {
private:

    class runner {
    public:
        virtual void execute(void) = 0;
    };

    template <class FUNC, typename T> class func_pair : public runner {
    private:
        FUNC &func;
        T param;
    public:
        func_pair(FUNC &f, const T &t) : func(f),param(t) { }
        void execute(void) {
            func(param);
        }
    };

    std::vector<runner*> funcQueue;

public:

    template <class FUNC, typename T> void add(FUNC &obj, const T &t) {
        funcQueue.push_back( new func_pair<FUNC,T>(obj,t) );
    }

    void run(void) {
        std::vector<runner*>::iterator itr=funcQueue.begin();
        for(;itr!=funcQueue.end();++itr) {
            (*itr)->execute();
            delete (*itr);
        }
    }
};

このアプローチは私のニーズに合う可能性がありますが、エントリごとに template_pair を割り当てて解放します。この手順はプロセス中に頻繁に呼び出されるため、これがメモリフラグメントを引き起こすかどうかはわかりません。

これを行うより良い方法はありますか?

4

2 に答える 2

7

関数オブジェクトとその引数を別々に渡す必要がありますか? boost::bindこの場合、次のようになります。

void check_temperature( int celsius )
{
    if(celsius > 26) {
        cooler.switch_on();
    }
};

void log_usage( std::string username ) 
{
    username.append(" logged in");
    syslog(LOG_NOTICE,username.c_str());
};

// keep actions
typedef std::vector< boost::function<void()> > func_arr_t;
func_arr_t actions;
actions.push_back( boost::bind( &log_usage, "administrator" ) );
actions.push_back( boost::bind( &check_temperature, lobbyMeter.read_temperature() ) );
actions.push_back( boost::bind( &log_usage, "lecture" ) );
actions.push_back( boost::bind( &check_temperature, classroomMeter.read_temperature() ) );

// run all
for ( func_arr_t::const_iterator it = actions.begin(); it != actions.end(); ++it )
    (*it)();

この場合command_sequence、関数オブジェクトの配列を保持するだけです。

于 2011-07-08T15:45:47.743 に答える
4

単項関数への引数は、シーケンスに追加する時点で固定されているように見えるため、boost::functionを使用してシーケンスに引数のない関数オブジェクトを受け入れさせ、必要なパラメーターをboost::bind することができます。

class command_sequence {
public:
    void add( boost::function<void(void)> functor );
};

/* ... as before ... */

log_usage logger;
check_temperature checker;

sequence.add( boost::bind<void>(logger, "administrator") );
sequence.add( boost::bind<void>(checker, lobbymeter.read_temperature()) );

関数オブジェクトの戻り値の型を自動的に推測できないため<void>、呼び出しのテンプレート パラメーターとして指定する必要があることに注意してください。または、これを回避するクラス定義でboost::bind呼び出される public typedef を公開できます。result_type

class log_usage
{
public:
    typedef void result_type;
    void operator() (const std::string& message)
    {
        // do stuff ...
    }
};

/* ... */

sequence.add(boost::bind(logger, "blah")); // will now compile
于 2011-07-08T15:59:35.037 に答える