Сериализация объектов функции

Можно ли сериализовать и десериализовать std::function, объект функции или закрытие вообще в С++? Как? Поддерживает ли С++ 11 это? Есть ли доступ к библиотечной поддержке для такой задачи (например, в Boost)?

Например, предположим, что программа на С++ имеет std::function, которая необходима для передачи (например, через сокет TCP/IP) другой программе на С++, находящейся на другой машине. Что вы предлагаете в таком сценарии?

<ч/" > Edit:

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

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

Ответ 1

Нет.

С++ не имеет встроенной поддержки сериализации и никогда не был задуман с идеей передачи кода из одного процесса в другой, чтобы одна машина не была другой. Языки, которые могут это делать, обычно включают как IR (промежуточное представление кода, независимого от машины), так и отражение.

Таким образом, вы остаетесь самим писать протокол для передачи необходимых действий, и подход DSL, безусловно, работоспособен... в зависимости от множества задач, которые вы хотите выполнить, и необходимости в производительности.

Другим решением будет переход на существующий язык. Например, база данных Redis NoSQL включает механизм LUA и может выполнять сценарии LUA, вы можете сделать то же самое и передать сценарии LUA в сети.

Ответ 2

Да для указателей функций и замыканий. Не для std::function.

Указатель функции является самым простым - это просто указатель, как любой другой, поэтому вы можете просто читать его как байты:

template <typename _Res, typename... _Args>
std::string serialize(_Res (*fn_ptr)(_Args...)) {
  return std::string(reinterpret_cast<const char*>(&fn_ptr), sizeof(fn_ptr));
}

template <typename _Res, typename... _Args>
_Res (*deserialize(std::string str))(_Args...) {
  return *reinterpret_cast<_Res (**)(_Args...)>(const_cast<char*>(str.c_str()));
}                   

Но я с удивлением обнаружил, что даже без перекомпиляции адрес функции будет изменяться при каждом вызове программы. Не очень полезно, если вы хотите передать адрес. Это связано с ASLR, которую вы можете отключить в Linux, запустив your_program с помощью setarch $(uname -m) -LR your_program.

Теперь вы можете отправить указатель на другую машину с той же программой и вызвать ее! (Это не связано с передачей исполняемого кода. Но если вы не создаете исполняемый код во время выполнения, я не думаю, что вы это ищете.)

Лямбда-функция совсем другая.

std::function<int(int)> addN(int N) {
  auto f = [=](int x){ return x + N; };
  return f;
}

Значение f будет зафиксировано int N. Его представление в памяти такое же, как int! Компилятор генерирует неназванный класс для лямбда, из которых f является экземпляром. Этот класс имеет operator(), перегруженный нашим кодом.

Недопустимый класс представляет проблему для сериализации. Это также представляет проблему для возврата лямбда-функций из функций. Последняя проблема решена с помощью std::function.

std::function, насколько я понимаю, реализуется путем создания шаблонного класса-оболочки, который фактически содержит ссылку на неназванный класс за функцией лямбда через параметр типа шаблона. (Это _Function_handler в functional.) std::function принимает указатель на статический метод (_M_invoke) этой оболочки класс и хранилища, которые плюс значение закрытия.

К сожалению, все зарывается в private, а размер значения закрытия не сохраняется. (Этого не нужно, потому что функция лямбда знает свой размер.)

Итак, std::function не поддается сериализации, но хорошо работает как проект. Я следил за тем, что он делает, упростил его (я только хотел сериализовать lambdas, а не множество других вызываемых вещей), сохранил размер значения закрытия в size_t и добавил методы для (де) сериализации. Он работает!