web-dev-qa-db-ja.com

関数オブジェクトを関数ラッパーに割り当てた後の予期しない動作

アプリケーションのバグを検索していましたが、最終的に修正しましたが、完全には理解できませんでした。この動作は、次の簡単なプログラムで再現できます。

#include <iostream>
#include <memory>
#include <functional>

struct Foo
{
  virtual int operator()(void) { return 1; } 
};

struct Bar : public Foo
{
  virtual int operator()(void) override { return 2; }
};

int main()
{
    std::shared_ptr<Foo> p = std::make_shared<Bar>();
    std::cout << (*p)() << std::endl;

    std::function<int(void)> f;
    f = *p;
    std::cout << f() << std::endl;

    return 0;
}

ラインの出力

std::cout << (*p)() << std::endl;

2、もちろん予想通りです。

しかし、行の出力

std::cout << f() << std::endl;

1。これは驚きました。割り当てf = *pは許可され、エラーは発生しません。

ラムダで修正したので、回避策は要求しません。
私の質問は、私がやったときに何が起こっているのかですf = *pと出力の理由1 のではなく 2

Gcc(MinGW)とVisual Studio 2019で問題を再現しました。
さらに私が言及したいのは、

Bar b;
std::function<int(void)> f1 = b;
std::cout << f1() << std::endl;

2、また。

35
Rabbid76

オブジェクトのスライスはここで行われます。

ポイントには_f = *p;_が与えられ、pは_std::shared_ptr<Foo>_型であり、_*p_の型は_Foo&_です(_Bar&_ではなく)。 _std::function_ の代入演算子も参照により引数を取りますが、

4)function(std::forward<F>(f)).swap(*this);を実行する場合と同様に、_*this_のターゲットを呼び出し可能なfに設定します。

上記のFも_Foo&_として推定されることに注意してください。そして _std::function_ のコンストラクタは値で引数を取り、オブジェクトのスライスが発生し、_*p_からスライスコピーされたf型のオブジェクトからFooが割り当てられるという効果になります。

_template< class F > 
function( F f );
_
24
songyuanyao

これは通常のスライスで、_std::function_および_std::shared_ptr_のレイヤーの下に隠されています。

_f = *p;
_

_*p_は適切なoperator()を持つ呼び出し可能なオブジェクトであり、これは_std::function_でラップできるものの1つであるため、有効です。

これが機能しない理由は、_*p_をコピーするためです。これは_Foo&_ではなく_Bar&_です。

最後の例のこの適応は同じように動作します。

_Bar b;
Foo& c = b;
std::function<int(void)> f1 = c;
std::cout << f1() << std::endl;
_
12
molbdnilo

スライス

これはスライスの場合です。その理由は、次のようにstd::functionの代入演算子です(別の answer でも示されています)。

Function(std :: forward(f))。swap(* this);を実行した場合と同様に、* thisのターゲットを呼び出し可能なfに設定します。この演算子は、fが引数型Args ...に対してCallableで、型Rを返さない限り、オーバーロードの解決に参加しません。(C++ 14以降)

https://en.cppreference.com/w/cpp/utility/functional/function/operator%3D

例を簡略化して取り除くと、何が起こっているのかが簡単にわかります。

Foo* p =  new Bar;

Foo f;
f = *p;//<-- slicing here since you deref and then copy the object

オーバーライドされた仮想関数へのポインターを取得することを目指していたようです-残念ながら、実装されている仮想関数ルックアップをunrollする簡単な方法はありませんruntimeルックアップテーブルを介して。ただし、簡単な回避策は、ラムダを使用してラップすることです(OPでも言及されています)。

f = [p]{return (*p)();};

より適切な解決策は、単にreference_wrapperを使用することでもあります。

f = std::ref(p);
11
darune

ポインターの静的型pFooです。

したがって、このステートメントでは

f = *p;

左オペランド*pのタイプはFooです。つまり、スライスがあります。

0