2011-01-29 13 views
9

私は、指定された特定の時間または繰り返しのパラメータとして与えられた別の関数を格納して繰り返す関数を作成しようとしています。 しかし、関数をパラメータとして渡す場合は、その前にすべてのパラメータを知る必要があります。 関数を1つのパラメータとして渡し、パラメータを別のパラメータとして渡す場合はどうすればよいですか?事前にC++:関数を(パラメータを知らないうちに)別の関数に渡すにはどうすればよいですか?

void AddTimer(float time, int repeats, void (*func), params); // I know params has no type and that (*func) is missing parameters but it is just to show you what I mean 

おかげ

答えて

3

現代のC++に関しては、戸惑いの答えは正しいです。

興味深いことに、C言語のシンプルでロバストなソリューションがあります。これは、C++で動作する限りです。任意のパラメータを許可する代わりに、関数をvoid (*func)(void*)と定義し、 "params"をvoid*とします。次に、パラメータを含む構造体を定義し、そのライフサイクルを管理するのは、呼び出し側の仕事です。あなたがしたい「火と忘れて」タイマーを、ので、あなたはこの方式を使用する場合にも道を必要とするかもしれない実際に

void myfunc(int, float); // defined elsewhere 

typedef struct { 
    int foo; 
    float bar; 
} myfunc_params; 

void myfunc_wrapper(void *p) { 
    myfunc_params *params = (myfunc_params *)p; 
    myfunc(params->foo, params->bar); 
} 

int main() { 
    myfunc_params x = {1, 2}; 
    AddTimer(23, 5, myfunc_wrapper, &x); 
    sleep(23*5 + 1); 
} 

:通常、発信者も本当にと呼ばれるように必要なの機能への単純なラッパーを記述しますすべてのファイアリングが完了すると、タイマーはユーザーデータポインタを解放します。

明らかに、このタイプの安全性は限られています。原則として、関数ポインタとユーザデータポインタを提供する人は、一致することを保証するのが非常に困難ではないので、問題ではありません。実際には、人々はバグを書き込む方法を見つけ、そのコンパイラがバグについて彼らに言わなかったためにあなたを責めます;-)

15

あなたが行うことができる最高のは、よく機能して引数をバインドするstd::bindまたはboost::bindと一緒に、引数としてstd::functionまたはboost::functionを使用している:

void foo() { std::cout << "foo" << std::endl; } 
void bar(int x) { std::cout << "bar(" << x << ")" << std::endl; } 
struct test { 
    void foo() { std::cout << "test::foo" << std::endl; } 
}; 
void call(int times, boost::function< void() > f) 
{ 
    for (int i = 0; i < times; ++i) 
     f(); 
} 
int main() { 
    call(1, &foo);     // no need to bind any argument 
    call(2, boost::bind(&bar, 5)); 
    test t; 
    call(1, boost::bind(&test::foo, &t)); // note the &t 
} 

完全に汎用の関数ポインタを渡すことに本質的に間違ったことがあることに注意してください。どのように使用しますか?未知の型の引数の定義されていない数を渡すことができるように、呼び出し関数の本体はどのように見えますか?これは、bindテンプレートが解決するものです。関数ポインタ(具体的な関数ポインタ)を呼び出し時に使用する引数のコピーとともに格納するクラスファンクタを作成します(この例の&tは、オブジェクトではなくポインタがコピーされることに注意してください) )。 bindの結果は、既知のインタフェースを介して呼び出すことができるファンクタです。この場合は、function< void() >の内部にバインドされ、引数なしで呼び出すことができます。

+0

は、void関数のみを呼び出しますか? – Ninja

+0

@忍者はい、もちろんです。呼び出し元として、戻り値の型がわからない場合は、どのように関数を使用できますか? 'boost :: function 'を使い、戻り値の型をあなたが望むものにキャストすることができます。現代のC++の代わりにしたい場合は、 'boost :: function 'を使ってある種の消去で再生することができます。 – kizzx2

1

実際に関数ポインタに関する規則が全くない場合は、void *を使用してください。

+5

'void(*)(void)'型の値を正しい関数ポインタ型にキャストすると 'void(*)(void)'は 'void'元の関数ポインタの値が保持されることが保証されます。関数のポインタ値を 'void *'にキャストして戻すと、これは保証されません。 –

+0

@Charles:あなたはどこで標準を読みましたか? –

+3

@DanielTrebbien:5.2.10 [expr.reinterpret.cast]/6. –

2

それはあなたが別の関数に関数ポインタを渡すことができる方法例でしかありませんし、それを呼び出す:

void AddTimer(float time, int repeats, void (*func)(int), int params) 
{ 
    //call the func 
    func(params); 
} 

void myfunction(int param) 
{ 
    //... 
} 

AddTimer(1000.0, 10, myfunction, 10); 

あなたの関数が異なる種類または/およびパラメータの数値を取る場合同様に、あなたのコードを書くことができます!

+0

+1、質問をもう一度読み直した後、引数の数と種類が不明であることはあまり確信していません。私よりも簡単な解決策です。 –

+0

これは私が実際に尋ねたものではありません。ダビデの答えはより多くのものであるように見えます。 – Ninja

0

C++ 11では、必要なものすべてが手に入りますあなたのタイマーを実装する。

バインドされた関数呼び出しを渡す最も簡潔な方法は、ラムダ構文を使用して生成されたファンクタを渡すことです(例:[]{ std::cout << "Hello, world!" << std::endl; })。このようにして生成されたオブジェクトは、コンパイラのみが知っている型を持っていますが、その型はstd::function<void()>に変換可能です。

#include <functional> 
#include <list> 
#include <chrono> 
#include <thread> 
#include <iostream> 

template <typename Clock = std::chrono::high_resolution_clock> 
class Timers { 
public: 
    using clock = Clock; 
    using duration = typename clock::duration; 
    using time_point = typename clock::time_point; 
private: 
    struct Timer { 
     duration const period; 
     std::function<void()> const call; 
     int repeats; 
     time_point next; 
     Timer(duration $period, int $repeats, std::function<void()> && $call) : 
     period($period), call(std::move($call)), repeats($repeats) {} 
    }; 
    std::list<Timer> m_timers; 
public: 
    Timers() {} 
    Timers(const Timers &) = delete; 
    Timers & operator=(const Timers &) = delete; 
    template <typename C> void add(std::chrono::milliseconds period, 
            int repeats, C && callable) 
    { 
     if (repeats) m_timers.push_back(Timer(period, repeats, callable)); 
    } 
    enum class Missed { Skip, Emit }; 
    void run(Missed missed = Missed::Emit) { 
     for (auto & timer : m_timers) timer.next = clock::now() + timer.period; 
     while (! m_timers.empty()) { 
     auto next = time_point::max(); 
     auto ti = std::begin(m_timers); 
     while (ti != std::end(m_timers)) { 
      while (ti->next <= clock::now()) { 
       ti->call(); 
       if (--ti->repeats <= 0) { 
        ti = m_timers.erase(ti); 
        continue; 
       } 
       do { 
        ti->next += ti->period; 
       } while (missed == Missed::Skip && ti->next <= clock::now()); 
      } 
      next = std::min(next, ti->next); 
      ++ ti; 
     } 
     if (! m_timers.empty()) std::this_thread::sleep_until(next); 
     } 
    } 
}; 

int main(void) 
{ 
    Timers<> timers; 
    using ms = std::chrono::milliseconds; 
    timers.add(ms(1000), 2, []{ std::cout << "Hello, world!" << std::endl; }); 
    timers.add(ms(100), 20, []{ std::cout << "*" << std::endl; }); 
    timers.run(); 
    std::cout << std::endl; 
    return 0; 
} 
関連する問題