Сериализация объектов функции
Можно ли сериализовать и десериализовать 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
и добавил методы для (де) сериализации. Он работает!