アプリケーションのバグを検索していましたが、最終的に修正しましたが、完全には理解できませんでした。この動作は、次の簡単なプログラムで再現できます。
#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
、また。
オブジェクトのスライスはここで行われます。
ポイントには_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 );
_
これは通常のスライスで、_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;
_
これはスライスの場合です。その理由は、次のように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);
ポインターの静的型p
はFoo
です。
したがって、このステートメントでは
f = *p;
左オペランド*p
のタイプはFoo
です。つまり、スライスがあります。