15

signal(int,void(*)(int))からの関数を使用して<csignal>、浮動小数点例外 SIGFPE を処理しようとしています。「浮動小数点例外」などのメッセージを表示するだけでなく、役立つ診断情報を出力できるようにしたいと考えています。これは、ハンドラーとして渡す関数signalが、コード内の一部のデータにアクセスする必要があることを意味します。そこに摩擦があります。

関数は、 type の 1 つのパラメーターのみを返しvoid、受け入れる必要がありますint。ハンドラーをデータ ストレージ クラスのメンバー関数にすることはできません。これは、タイプがvoid(Foo::*)(int)非表示のthisポインターによるものになるためです。

ラムダを使用して、このような匿名関数を作成しようと考えました。

void handler(int nSig, Foo data)
{
    // do something
}
// snip
Foo data;
signal(SIGFPE, [&](int nSig)->void{handler(nSig,data);});

ただし、ラムダは変数dataを外部からキャプチャするため、コンパイラはそれをポインターにキャストできませんvoid(*)(int)(これはラムダの理想的な使用法のように見えるため、残念です)。

単純にdataグローバル変数を作成することもできますが、それhandlerは明らかな理由でこれを行うのが嫌いです。

私の質問はこうです。C++ で無名関数を模倣する最良の方法は何ですか?

注: 私はネイティブ C++ ソリューションを好み、boost または同等のものを使用する必要はありません。

4

3 に答える 3

13

これは確かに良い質問です。ただし、C++ を非難する前に、何が起こっているのかを把握しましょう。ラムダがどのように実装されているかを考えてみてください。

最も単純なラムダは、データがキャプチャされない場合です。その場合、その基になる型は単純な単純な関数になります。たとえば、次のようなラムダです。

[] (int p0) {}

単純な関数と同等になります。

void foo(int p0)
{
}

そのラムダを関数ポインターにしたい場合、これは実際には完全に機能します。例えば:

#include <string>
#include <csignal>
#include <iostream>

int main()
{
    int ret;
    signal(SIGINT, [](int signal) {
            std::cout << "Got signal " << signal << std::endl;
        });
    std::cin >> ret;
    return ret;
}

ここまでは順調ですね。しかし、ここで、いくつかのデータをシグナル ハンドラーに関連付けたいと考えています (ちなみに、シグナル ハンドラー内でしかシグナル セーフ コードを実行できないため、上記のコードは未定義の動作です)。したがって、次のようなラムダが必要です。

#include <string>
#include <csignal>
#include <iostream>

struct handler_context {
    std::string code;
    std::string desc;
};

int main()
{
    int ret;
    handler_context ctx({ "SIGINT", "Interrupt" });
    signal(SIGINT, [&](int signal) {
            std::cout << "Got signal " << signal
                      << " (" << ctx.code << ": " << ctx.desc
                      << ")\n" << std::flush;
        });
    std::cin >> ret;
    return ret;
}

C++ ラムダのシンタックス シュガーのことはしばらく忘れましょう。C やアセンブラーでもラムダを「模倣」できることは周知の事実です。実際、それはどのように見えるでしょうか?C スタイルの「ラムダ」は次のようになります (これはまだ C++ です)。

#include <string>
#include <cstdlib>
#include <iostream>

/*
 * This is a context associated with our lambda function.
 * Some dummy variables, for the sake of example.
 */
struct lambda_captures {
    int v0;
    int v1;
};

static int lambda_func(int p0, void *ctx) // <-- This is our lambda "function".
{
    lambda_captures *captures = (lambda_captures *)ctx;
    std::cout << "Got " << p0 << " (ctx: "
              << captures->v0 << ", " << captures->v1
              << ")\n" << std::flush;
    return 0;
}

// Below is an example of API function provided to the user that can
// invoke a callback supplied by the user.
static void some_api_function(int (*callback)(int p, void *data), void *data)
{
    callback(12345, data);
    callback(98765, data);
}

int main()
{
    lambda_captures captures;
    captures.v0 = 1986;
    captures.v1 = 2012;

    some_api_function(lambda_func, (void *)&captures);

    return EXIT_SUCCESS;
}

上記は C スタイルです。C++ は "context" を "this" として渡す傾向があり、これは常に暗黙的な最初の引数です。API が最初の引数として「データ」を渡すことをサポートしている場合、ポインターからメンバーへの変換 (PMF) を適用して、次のように記述できます。

#include <string>
#include <cstdlib>
#include <iostream>

struct some_class {
    int v0;
    int v1;

    int func(int p0)
    {
        std::cout << "Got " << p0 << " (ctx: "
                  << v0 << ", " << v1
                  << ")\n" << std::flush;
        return p0;
    }
};

static void some_api_function(int (*callback)(void *data, int p), void *data)
{
    callback(data, 12345);
    callback(data, 98765);
}

int main()
{
    typedef int (*mpf_type)(void *, int);

    some_class clazz({ 1986, 2012 }); // <- Note a bit of a Java style :-)
    some_api_function((mpf_type)&some_class::func, (void *)&clazz);

    return EXIT_SUCCESS;
}

上記の 2 つの例では、「データ」が常に渡されることに注意してください。これはとても重要です。コールバックを呼び出すはずの API が、何らかの形でコールバックに返される「void *」ポインタを受け入れない場合、コンテキストをコールバックに関連付けることはできません。唯一の例外はグローバル データです。たとえば、次の API は不適切です。

#include <string>
#include <cstdlib>
#include <iostream>

struct lambda_captures {
    int v0;
    int v1;
};

static int lambda_func(int p0)
{
/*
    // WHERE DO WE GET OUR "lambda_captures" OBJECT FROM????
    lambda_captures *captures = (lambda_captures *)ctx;
    std::cout << "Got " << p0 << " (ctx: "
              << captures->v0 << ", " << captures->v1
              << ")\n" << std::flush;
*/
    return 0;
}

// Below is an example of API function provided to the user that can
// invoke a callback supplied by the user.
static void some_api_function(int (*callback)(int p))
{
    callback(12345);
    callback(98765);
}

int main()
{
    lambda_captures captures;
    captures.v0 = 1986;
    captures.v1 = 2012;

    some_api_function(lambda_func /* How do we pass a context??? */);

    return EXIT_SUCCESS;
}

そうは言っても、古いシグナル API はまさにそのようなものです。この問題を回避する唯一の方法は、実際に「コンテキスト」をグローバル スコープに入れることです。次に、アドレスがよく知られているため、シグナルハンドラー関数がそれにアクセスできます。たとえば、次のようになります。

#include <string>
#include <cstdlib>
#include <iostream>

struct lambda_captures {
    int v0;
    int v1;
};

lambda_captures captures({ 1986, 2012 }); // Whoa-la!!!

static int lambda_func(int p0)
{
    std::cout << "Got " << p0 << " (ctx: "
              << captures.v0 << ", " << captures.v1
              << ")\n" << std::flush;
    return 0;
}

// Below is an example of API function provided to the user that can
// invoke a callback supplied by the user.
static void some_api_function(int (*callback)(int p))
{
    callback(12345);
    callback(98765);
}

int main()
{
    some_api_function(lambda_func);

    return EXIT_SUCCESS;
}

これは人々が対処しなければならないことです。シグナルAPIの場合だけではありません。これは他のことにも当てはまります。たとえば、割り込みハンドラの処理です。しかし、ハードウェアを扱わなければならない低レベルのプログラミング。もちろん、この種の API をユーザー空間に提供することは、最善のアイデアではありませんでした。もう一度言いますが、シグナルハンドラでできることはごくわずかです。async-signal-safe functionsのみを呼び出すことができます。

もちろん、古い API は実際には POSIX 標準であるため、すぐになくなることはありません。ただし、開発者は問題を認識しており、シグナルを処理するためのより良い方法があります。たとえば Linux では、 を使用eventfdしてシグナル ハンドラをインストールし、それを任意のコンテキストに関連付けて、コールバック関数で必要なことを何でも行うことができます。

とにかく、遊んでいたラムダに戻りましょう。問題は C++ ではなく、グローバル変数を使用する以外にコンテキストを渡す方法がないシグナル API にあります。そうは言っても、ラムダでも機能します。

#include <string>
#include <cstdlib>
#include <csignal>
#include <iostream>

struct some_data {
    std::string code;
    std::string desc;
};

static some_data data({ "SIGING", "Interrupt" });

int main()
{
    signal(SIGINT, [](int signal) {
            std::cout << "Got " << signal << " (" << data.code << ", "
                      << data.desc << ")\n" << std::flush;
        });
    return EXIT_SUCCESS;
}

したがって、C++ がここで行っていることは正しいことなので、恥ずかしいことではありません。

于 2012-05-30T13:31:31.763 に答える
8

C には無名関数のようなものはありません (関数は C 呼び出し規則に従わなければならないため、C++ はここでは関係ありません)。

あなたができる唯一のことは、ハンドラーからグローバルにアクセスすることです。おそらくグローバル変数です(定数ではなく、問題ありません)。

マルチスレッドの問題を回避するために、これらのグローバルをスレッド ローカルにすることをお勧めしますが、グローバル変数がアプリケーションをより脆くするという意味では、それでも良くありません。


方法 ?

注: Luc Danton が辛抱強く説明してくれたように、シグナルは非アトミック アクティビティを中断する可能性があるため、グローバルからの読み取りは、ロックのないアトミック (またはその他のいくつか) である場合にのみ安全です。残念ながらstd::functionそうではないかもしれません。実装によってはstd::functionアクセスがアトミックである場合にどのように実行できるかを説明するために、このコードを残しておきます。

ステートフルなものを呼び出し、スレッドを分離し、再入可能な呼び出しを許可するトランポリンを作成することができます。

typedef std::function<void(int)> SignalHandlerType;

extern thread_local ignalHandlerType SignalHandler;

そして、次のアクセサーを作成します (シグナルに渡されます):

void handle_signal(int const i) {
    if (SignalHandler) { SignalHandler(i); }
}

次の RAII セッターと同様に:

class SignalSetter: boost::noncopyable {
public:
    SignalSetter(int signal, SignalHandlerType&& sh):
        signal(signal), chandler(0), handler(sh)
    {
        chandler = std::signal(signal, &handle_signal<T>);
        swap(SignalHandler, handler);
    }

    ~SignalSetter() {
        std::signal(signal, chandler);
        swap(SignalHandler, handler);
    }

private:
    typedef void(*CHandlerType)(int);

    int signal;
    CHandlerType chandler;
    SignalHandlerType handler;
};

注:グローバル変数と の両方がクラスにある可能性があります...しかし、そうhandle_signalではないため...privateSignalSetterstd::signal

予想される使用法:

int main(int argc, char* argv[]) {
    SignalSetter setter(SIGFPE, [argc, argv]() {
        std::cout << argc << ": " << argc << std::endl;
    });

    // do what you want.
}
于 2012-05-30T12:25:54.683 に答える
0

実行時に新しい静的関数を簡単に作成することはできません。一部の JIT コンパイラ ライブラリではこれを行うことができます。適切な数のポインターのみが必要な場合は、テンプレートを特殊化して静的関数のプールを作成できます。

最も簡単な方法は、静的関数で C++ ファンクターをラップすることです。ここでの問題は、ユーザー データ パラメータのようなものがないことです。パラメータは 1 つだけで、シグナルの数です。シグナルは 64 個しかないため、静的配列を作成し、std::function< void(int) >シグナル番号に応じてそれぞれを呼び出すことができます。簡単な例:

typedef std::function< void(int) > SignalFunc;

static std::array< SignalFunc, 64 > signalsFunc;

static void cHandler(int nSig)
{
    signalsFunc.at(nSig)(nSig);
}

SignalFunc RegisterSystemSignal( int sig, SignalFunc func )
{
    if( signal( sig, func ? &cHandler : (sighandler_t)SIG_DFL ) != SIG_ERR )
    { 
        func.swap( signalsFunc.at( sig ) );
        return func;
    }
    throw some_error();
}

だから今、あなたはそれを行うことができます:

RegisterSystemSignal(SIGFPE, [&](int nSig)->void{handler(nSig,data);});

sigactionより多くの機能を持っている魔女もあります。

于 2012-05-30T15:38:13.123 に答える