2012-05-30 4 views
15

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

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

Я думал об использовании lambdas, чтобы попытаться сделать анонимную функцию следующим образом;

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 или эквивалент.

+0

Что значит вы не можете использовать 'data' в' void (*) (int) '? 'data' - это экземпляр' Foo' ... Почему вы не можете называть 'data.handler (nSig)'? – David

+0

Если я делаю 'handler' членом класса' Foo', то это сигнатура функции теперь 'void (Foo :: *) (int)' not 'void (*) (int)' причина в том, что нестатический член функции имеют скрытый указатель на вызывающий объект ('this' является скрытым первым аргументом) :-( – Dan

+0

@Dave Он имел в виду, что он не может наложить * лямбда *. –

ответ

8

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

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

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


Как это сделать?

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

Я бы не сказал, что C++ не имеет значения, поскольку, если C++ предложила прокладку от захватов к указателям на функции, это сработает. И обратите внимание, что, хотя C++ делает * не * предлагать такой способ (по хорошим техническим причинам), это на самом деле невозможно. –

+0

@ Konrad: Это интересно. У вас есть дополнительная информация о хороших технических причинах? Я предполагаю, что этой функции понадобится какой-то динамически созданный батутный код –

+1

@NiklasB. Ну, замыкания реализуются через функторы - т. Е. Структуры, которые перегружают 'operator()'. Вы можете получить указатель на эту функцию, но это указатель на функцию * member * и несовместим с обычным указателем функции. Чтобы получить нормальный указатель функции, вам нужна * бесплатная функция.Вы можете подумать, что батут (но батут обычно получает его вызов, переданный в качестве аргумента, и это не работает здесь, так как это нарушит подпись, вам действительно нужен глобальный объект или что-то еще, как предположил Маттие). –

12

Это действительно хороший вопрос. Давайте выясним, что происходит, прежде чем обвинять 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; 
} 

Пока все хорошо. Но теперь вы хотите связать некоторые данные с вашим обработчиком сигнала (кстати, код выше является неопределенным поведением как you can only execute signal-safe code inside a signal handler). Таким образом, вы хотите, лямбда, как:

#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++, как правило, проходит «контекст», как «это», которое всегда неявно первый аргумент , Если наш API поддерживается прохождение «данных» в качестве первого аргумента, мы могли бы применить указатель преобразование членов (ИМП) и написать что-то вроде этого:

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

+2

Обратите внимание, что использование (безстоящей) лямбда формально не определено, потому что преобразование дает указатель на функцию с неопределенной связью, тогда как нам нужна C-связь. Я также направляю вас [мой другой комментарий] (http://stackoverflow.com/questions/10816107/anonymous-function-c#comment14079603_10816266). –

+4

, так что у этого ответа есть конец :) –

+2

+1 для асинхронного упоминания. –

0

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

Таким образом, самый простой способ - обернуть 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 колдуньи имеет больше возможностей.

Смежные вопросы