Можно ли сериализовать и десериализовать std::function
, функциональный объект или замыкание вообще в C ++? Как? C ++ 11 облегчает это? Есть ли какая-либо библиотека поддержки для такой задачи (например, в Boost)?
Например, предположим, что программа на C ++ имеет std::function
который необходимо передать (скажем, через сокет TCP / IP) другой программе C ++, находящейся на другой машине. Что вы предлагаете в таком сценарии?
Редактировать:
Чтобы уточнить, функции, которые должны быть перемещены, должны быть чистыми и без побочных эффектов. Поэтому у меня нет проблем с безопасностью или несоответствием состояния.
Решение проблемы заключается в создании небольшого встроенного домена для конкретного языка и сериализации его абстрактного синтаксического дерева.
Я надеялся, что смогу найти поддержку языка / библиотеки для перемещения машинно-независимого представления функций.
Нет.
C ++ не имеет встроенной поддержки сериализации и никогда не задумывался о идее передачи кода от одного процесса другому, чтобы одна машина не переместилась на другую. Языки, которые могут это делать, обычно имеют как IR (промежуточное представление кода, который не зависит от машины), так и рефлексию.
Таким образом, вам остается написать протокол для передачи нужных вам действий, и подход DSL, безусловно, работает … в зависимости от множества задач, которые вы хотите выполнить, и потребности в производительности.
Другим решением было бы пойти с существующим языком. Например, база данных Redis NoSQL встраивает механизм LUA и может выполнять сценарии LUA, вы можете сделать то же самое и передавать сценарии LUA по сети.
Да для функциональных указателей и замыканий. Не для 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
в функциональная.) std::function
принимает указатель на функцию статического метода (_M_invoke
) этого класса-обертки и сохраняет его плюс значение замыкания.
К сожалению, все похоронено в private
члены и размер значения закрытия не сохраняются. (Это не нужно, потому что лямбда-функция знает свой размер.)
Так std::function
не поддается сериализации, но хорошо работает как проект. Я следовал за тем, что он делает, сильно упростил его (я только хотел сериализовать лямбды, а не множество других вызываемых вещей), сохранил размер значения замыкания в size_t
и добавлены методы для (де) сериализации. Оно работает!
Нет, но есть некоторые ограниченные решения.
Максимум, на что вы можете надеяться, — это зарегистрировать функции в некоторой глобальной карте (например, с ключевыми строками), которая является общей для отправляющего и получающего кода (либо на разных компьютерах, либо до и после сериализации).
Затем вы можете сериализовать строку, связанную с функцией, и получить ее на другой стороне.
В качестве конкретного примера библиотека HPX реализует что-то вроде этого, что-то под названием HPX_ACTION.
Это требует большого количества протоколов и является хрупким в отношении изменений в коде.
Но в конце концов это ничем не отличается от того, что пытается сериализовать класс с частными данными. В некотором смысле код функции является ее частной частью (аргументы и интерфейс возврата являются публичной частью).
Что оставляет у вас надежду, так это то, что в зависимости от того, как вы организуете код, эти «объекты» могут быть глобальными или общими, и, если все пойдет хорошо, они будут доступны во время сериализации и десериализации посредством некоторой предопределенной косвенной зависимости во время выполнения.
Это грубый пример:
код сериализатора:
// common:
class C{
double d;
public:
C(double d) : d(d){}
operator(double x) const{return d*x;}
};
C c1{1.};
C c2{2.};
std::map<std::string, C*> const m{{"c1", &c1}, {"c2", &c2}};
// :common
main(int argc, char** argv){
C* f = (argc == 2)?&c1:&c2;
(*f)(5.); // print 5 or 10 depending on the runtime args
serialize(f); // somehow write "c1" or "c2" to a file
}
код десериализатора:
// common:
class C{
double d;
public:
operator(double x){return d*x;}
};
C c1;
C c2;
std::map<std::string, C*> const m{{"c1", &c1}, {"c2", &c2}};
// :common
main(){
C* f;
deserialize(f); // somehow read "c1" or "c2" and assign the pointer from the translation "map"(*f)(3.); // print 3 or 6 depending on the code of the **other** run
}
(код не проверен).
Обратите внимание, что это требует много общего и непротиворечивого кода, но в зависимости от среды вы можете гарантировать это.
Малейшее изменение в коде может привести к трудно обнаруживаемой логической ошибке.
Кроме того, я играл здесь с глобальными объектами (которые могут использоваться в свободных функциях), но то же самое можно сделать с объектами с областью видимости, что становится сложнее, как установить карту локально (#include
общий код внутри локальной области видимости?)