16

std::functionC ++で一般的に、、関数オブジェクト、またはクロージャをシリアル化および逆シリアル化することは可能ですか?どのように?C ++ 11はこれを容易にしますか?そのようなタスクに利用できるライブラリサポートはありますか(たとえば、Boostで)?

たとえば、C ++プログラムにstd::function、別のマシンにある別のC ++プログラムに(たとえばTCP / IPソケットを介して)通信する必要があるがあるとします。そのようなシナリオで何を提案しますか?


編集:

明確にするために、移動される関数は純粋で副作用のないものであると想定されています。したがって、セキュリティや状態の不一致の問題はありません。

この問題の解決策は、小さな組み込みドメイン固有言語を構築し、その抽象構文ツリーをシリアル化することです。代わりに、マシンに依存しない関数の表現を移動するための言語/ライブラリのサポートが見つかることを期待していました。

4

3 に答える 3

12

関数ポインタとクロージャの場合ははい。ではありません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()));
}                   

しかし、再コンパイルしなくても、プログラムを呼び出すたびに関数のアドレスが変わることに驚きました。アドレスを送信したい場合はあまり役に立ちません。これはASLRyour_programによるもので、Linuxでは。から始めることでオフにできます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)シリアル化のメソッドを追加しました。できます!

于 2014-03-31T21:08:49.220 に答える
8

いいえ。

C ++にはシリアル化のサポートが組み込まれておらず、あるマシンから別のマシンにコードを送信しないように、あるプロセスから別のプロセスにコードを送信するというアイデアは考えられていませんでした。そうする可能性のある言語は、通常、IR(マシンに依存しないコードの中間表現)とリフレクションの両方を備えています。

したがって、必要なアクションを送信するためのプロトコルを自分で作成する必要があります。DSLアプローチは、実行するさまざまなタスクとパフォーマンスの必要性に応じて、確実に機能します。

別の解決策は、既存の言語を使用することです。たとえば、Redis NoSQLデータベースにはLUAエンジンが組み込まれており、LUAスクリプトを実行する場合があります。同じことを実行して、ネットワーク上でLUAスクリプトを送信できます。

于 2012-09-09T14:04:36.683 に答える
0

いいえ。ただし、いくつかの制限された解決策があります。

最も期待できるのは、送信コードと受信コード(異なるコンピューター内またはシリアル化の前後のいずれか)に共通するある種のグローバルマップ(キー文字列など)に関数を登録することです。次に、関数に関連付けられた文字列をシリアル化して、反対側で取得できます。

具体的な例として、ライブラリ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ローカルスコープ内の共通コード?)

于 2018-10-10T08:54:48.380 に答える