Вопрос по c++, c++11 – Анонимная функция C ++

15

Я пытаюсь использовать функциюsignal(int,void(*)(int)) от<csignal> для обработки исключения с плавающей запятой SIGFPE. Я хотел бы иметь возможность распечатать полезную диагностику, кроме сообщения «Исключение с плавающей точкой» или что-то в этом роде. Это означает, что функция, которую я передаю в качестве обработчикаsignal нужен доступ к некоторым данным в моем коде. В этом и заключается загвоздка.

Функция должна возвращатьvoid и принимайте только 1 параметр типаint. Я не могу сделать обработчик функцией-членом моего класса хранения данных, так как тогда тип будетvoid(Foo::*)(int) из-за скрытогоthis указатель.

Я думал об использовании лямбда-выражений для создания анонимной функции, подобной этой;

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

however, потому что лямбда-переменная захватывает переменнуюdata извне компилятор не позволит привести его к указателю наvoid(*)(int) (это позор, так как это кажется идеальным использованием для лямбд).

Я мог бы просто сделатьdata глобальная переменная, которую затем можно увидеть вhandler но я не хочу этого делать по понятным причинам.

Так что мой вопрос таков; Какой лучший способ имитировать анонимные функции в C ++?

Примечание: я бы предпочел нативное решение C ++, а не использовать boost или его эквивалент.

Извините, я вижу, как моя формулировка может быть неверно истолкована. Dan
Что ты имеешь в виду, что не можешь сыгратьdata вvoid(*)(int)? data является экземпляромFoo ... Почему ты не можешь позвонитьdata.handler(nSig)? David
Обратите внимание, что существует sigaction () и флаг SA_SIGINFO, который может быть немного лучше для ваших нужд предоставления дополнительной информации (что, однако, ничего не меняет в исходной части вопроса) PlasmaHH
Если я сделаюhandler член классаFoo тогда это подпись функции теперьvoid(Foo::*)(int) неvoid(*)(int) причина в том, что нестатические функции-члены имеют скрытый указатель на вызывающий объект this - скрытый первый аргумент): - Dan
@ Дейв Он имел в виду, что не может разыграть Лямбда. Konrad Rudolph

Ваш Ответ

3   ответа
8

как анонимная функция (C ++ здесь не имеет значения, так как функция должна соблюдать соглашение о вызовах C).

Единственное, что ты можешь сделать, это Мурашки доступ к глобальным переменным из обработчика, возможно, глобальный Переменные (а не константы, что было бы хорошо).

Я советую сделать эти глобальные thread local чтобы избежать проблем с многопоточностью, но это все ещеПлох в том смысле, что глобальные переменные создают более хрупкие приложения.

Как

Примечание: как терпеливо объяснил мне Люк Дантон, сигнал может прервать любую неатомарную деятельность, и, таким образом, чтение из глобального объекта безопасно только в том случае, если это атом без блокировки (или несколько других вещей).К несчастью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 может бытьprivate наSignalSetter класс ... но так какstd::signal не является..

Ожидаемое использование:

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

    // do what you want.
}
@ Дэн: Насколько я знаю. Я реализовал пример использования RAII и локального состояния потока Батут свернуто в одно: D Matthieu M.
Я тоже упустил из виду, но формально типCHandlerType должна быть функцией с C-связью. (Это должно бытьextern "C" typedef void (*CHandlerType)(int); Я думаю, но не в классе). По аналогииhandle_signal должна иметь связь С. Luc Danton
Я бы не сказал, что C ++ не имеет значения, поскольку, если бы C ++ предлагал переход от перехватов к указателям на функции, это сработало бы. И обратите внимание, что в то время как C ++ делаетн предложить такой способ (по веским техническим причинам) на самом деле не невозможно. Konrad Rudolph
@ Конрад: это интересно. У вас есть больше информации о хороших технических причинах? Я полагаю, что для этой функции потребуется какой-то динамически генерируемый батутный код Niklas B.
@ NiklasB. Ну, замыкания реализуются через функторы - то есть структуры, которые перегружаютoperator(). Вы можете получить указатель на эту функцию, но это указатель на функц и несовместимо с обычным указателем на функцию. Чтобы получить нормальный указатель на функцию, вынужн бесплатная функция. Вы можете считать, что батут (но батут обычно передает своего вызываемого абонента в качестве аргумента, и это не сработает, поскольку это может нарушить сигнатуру, вам на самом деле нужен глобальный объект или что-то еще, как предложил Матье). Konrad Rudolph
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 ++):

#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;
}

Above - это стиль 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;
}

В приведенных выше двух примерах обратите внимание, что «данные» всегда передаются. Это очень важно. Если 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 в пользовательском пространстве было не лучшей идеей. И я упомяну это еще раз - в обработчике сигналов можно сделать лишь небольшой набор вещей. Вы можете только позвонить асинхронно-сигнальные безопасные функции.

Конечно, старый 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;
}

Поэтому в том, что здесь делает С ++, нет ничего постыдного, потому что это правильно.

Обратите внимание, что использование лямбды (без сохранения состояния) формально не определено должным образом, потому что преобразование дает указатель на функцию с неопределенной связью, тогда как нам нужна связь Си. Я также направляю вас на мой другой комментарий. Luc Danton
так что у этого ответа есть конец Mr.Anubis
+ 1 для асинхронного упоминания. Matthieu M.
0

ия, некоторые библиотеки JIT-компиляторов могут это сделать. Если вам нужно только разумное количество указателей, вы можете создать пул статических функций, специализируя шаблон.

Самый простой способ - обернуть функторы C ++ статической функцией. Проблема здесь заключается в том, что нет ничего похожего на параметр пользовательских данных. Есть только один параметр, это номер сигнала. Поскольку существует всего 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 Ведьма имеет больше возможностей.

Похожие вопросы