1

最近、 boost :: bindを使用しているときに、コードにバグが発生しました。

boost :: bind docsから:

bindが取る引数は、返された関数オブジェクトによってコピーされ、内部的に保持されます。

保持されていたコピーのタイプは、関数のシグネチャに基づいていると思いました。ただし、実際には、渡された値のタイプに基づいています。

私の場合、バインド式で使用される型を関数が受け取る型に変換するために、暗黙の変換が行われていました。この変換はバインドのサイトで発生すると予想していましたが、結果の関数オブジェクトが使用されたときに発生します。

振り返ってみると、boost :: bindを使用すると、型が結合部位ではなく呼び出し部位でのみ互換性がない場合にエラーが発生するという事実から、これを理解できたはずです。

私の質問は:なぜboost :: bindがこのように機能するのですか?

  1. コンパイラのエラーメッセージが悪化しているようです
  2. 暗黙の変換が発生し、ファンクターへの呼び出しが複数ある場合は、効率が低下するようです。

しかし、Boostがどれだけうまく設計されているかを考えると、理由があると思います。std :: bind1st / bind2ndから継承された動作でしたか?これを実装するのが難しい/不可能であるという微妙な理由はありますか?完全に他に何か?

その2番目の理論をテストするために、機能しているように見える小さなコードスニペットを作成しましたが、それは単なるフラグメントであるため、説明していないバインドの機能がある可能性があります。

namespace b = boost;
template<class R, class B1, class A1>
   b::_bi::bind_t<R, R (*) (B1), typename b::_bi::list_av_1<B1>::type>
   mybind(R (*f) (B1), A1 a1)
{
   typedef R (*F) (B1);
   typedef typename b::_bi::list_av_1<B1>::type list_type;
   return b::_bi::bind_t<R, F, list_type> (f, list_type(B1(a1)));
}

struct Convertible
{
   Convertible(int a) : b(a) {}
   int b;
};

int foo(Convertible bar)
{
   return 2+bar.b;
}

void mainFunc()
{
   int x = 3;
   b::function<int()> funcObj = mybind(foo, x);
   printf("val: %d\n", funcObj());
}
4

4 に答える 4

4

ファンクターは複数のオーバーロードをサポートしている可能性があるため、異なる動作を与える可能性があります。すべての引数を知っていれば(そして標準C ++がこの機能を保証できるかどうかはわかりませんが)この署名を解決できたとしても、すべての引数を知っているbindわけではないため、確実に提供することはできません。したがって、bind必要な情報を持っていません。

編集:明確にするために、検討してください

struct x {
    void operator()(int, std::vector<float>);
    void operator()(float, std::string);
};

int main() {
    auto b = std::bind(x(), 1); // convert or not?
}

1構造体を熟考し、そのオーバーロードについての知識を得たとしても、をフロートに変換する必要があるかどうかについてはまだ決定不可能です。

于 2012-06-29T02:12:56.000 に答える
2

これは、bindが、関数ポインター、、またはを使用した独自のファンクターなど、呼び出し可能なエンティティと連携する必要があるためだと思いstd::function<>ます。これにより、を使用して呼び出すことができるすべてのタイプでバインドが汎用になります。つまり、ファンクターに対するBindの暗黙の要件は、structoperator()()()

bindが関数の引数の型を格納する場合、型パラメーターとして渡された呼び出し可能なエンティティについて、何らかの方法でそれらを推測する必要があります。operator()渡された構造体型のパラメータ型を推測することは、ユーザーが何らかの種類を指定することに依存しない限り不可能であるため、これは明らかに一般的ではありませんtypedef(例として)。その結果、ファンクター(またはコンセプト)の要件はもはや具体的/単純ではありません。

これが理由かどうかは完全にはわかりませんが、問題になることの1つです。

編集:DeadMGが別の回答で述べているように、コンパイラーはファンクタータイプを解決できないため、オーバーロードは標準の関数ポインターに対してもあいまいさを生み出します。バインドするために提供するタイプを保存して使用することにより()、この問題も回避されます。

于 2012-06-29T02:09:16.727 に答える
2

呼び出しサイトで引数を処理する必要がある場合があります。

最初のそのような例は、メンバー関数を呼び出すことです。ここでは、boost::bind( &std::vector<int>::push_back, myvector)おそらく不要なオブジェクト()のコピーでメンバーを呼び出すことができます。そうでない場合は、ポインターを渡す必要があり、バインダーはポインターを逆参照します。必要に応じて(boost::bind( &std::vector<int>::push_back, &myvector ))-両方のオプションが異なるプログラムで意味をなす可能性があることに注意してください

もう1つの重要なユースケースは、関数を参照して引数を渡すことです。値渡し呼び出しと同等の実行bindコピーします。ライブラリには、渡される実際のオブジェクトへのポインタを格納するヘルパー関数refとを介して引数をラップするオプションがcrefあり、呼び出しの場所で(暗黙の変換を介して)ポインタを逆参照します。ターゲットタイプへの変換がバインド時に実行された場合、これを実装することは不可能です。

于 2012-06-29T02:54:40.183 に答える
0

良い例は、「std::future」を通常の型をとるいくつかの通常の関数にバインドすることです。

通常のf(x、y)関数を信じられないほど非同期的に使用したいとします。つまり、「f(X.get()、Y.get())」のように呼びたいのです。これには十分な理由があります。その行を呼び出すだけで、両方の入力が使用可能になるとすぐにfのロジックが実行されます(結合に個別のコード行は必要ありません)。これを行うには、次のものが必要です。

1)暗黙の変換「std :: future<T>->T」をサポートする必要があります。これは、std::futureまたは同等のカスタムにはキャスト演算子が必要であることを意味します。

operator T() { return get(); }

2)次に、ジェネリック関数をバインドして、そのすべてのパラメーターを非表示にする必要があります

// Hide the parameters
template<typename OUTPUT, typename... INPUTS>
std::function<OUTPUT()> BindVariadic(std::function<OUTPUT(INPUTS...)> f,
                                     INPUTS&&... in)
{
   std::function<OUTPUT()> stub = std::bind( f, std::forward<INPUTS>(in)...);
   return stub;
}

呼び出し時に「std::function<T>->T」変換を行うstd::bindを使用すると、実際に「stub()」を呼び出したときにすべての入力パラメーターが使用可能になるのを待つだけです。バインド時に演算子T()を介して変換を行った場合、ロジックは、実際に「スタブ」を作成したときに、それを使用するときではなく、サイレントに待機を強制します。「stub()」が、私が作成したのと同じスレッドで常に安全に実行できるとは限らない場合、これは致命的となる可能性があります。

その設計の選択を余儀なくされた他のユースケースもあります。この非同期処理用の手の込んだものは、私が個人的によく知っているものです。

于 2014-01-24T05:30:40.527 に答える