Unexpected behavior after assignment of function object to function wrapper

Rab*_*d76 34 c++ object-slicing c++11

I was searching a bug in an application, which I've finally fixed but didn't understand completely. The behavior can be reproduced with the following simple program:

#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;
}
Run Code Online (Sandbox Code Playgroud)

The output of the line

std::cout << (*p)() << std::endl;
Run Code Online (Sandbox Code Playgroud)

is 2, which is as I expected, of course.

But the output of the line

std::cout << f() << std::endl;
Run Code Online (Sandbox Code Playgroud)

1。这让我感到惊讶。我什至惊讶于分配f = *p是允许的,并且不会导致错误。

我不需要解决方法,因为我通过lambda对其进行了修复。
我的问题是,当我这样做时会发生什么f = *p,为什么输出1而不是2

我已经重现了gcc(MinGW)和Visual Studio 2019的问题。
此外,我想提及的是

Bar b;
std::function<int(void)> f1 = b;
std::cout << f1() << std::endl;
Run Code Online (Sandbox Code Playgroud)

2,一次。

son*_*yao 24

对象切片在这里发生。

给定的点是f = *p;p类型为std::shared_ptr<Foo>,则类型*pFoo&(而不是Bar&)。即使是的赋值运算符也std::function可以通过引用引用参数,但是

4)将目标设置为*this可调用对象f,就像执行一样function(std::forward<F>(f)).swap(*this);

注意,F上面的推论也是Foo&如此。然后按值接受参数的构造函数,std::function发生对象切片,效果变为fFoo切片复制的类型的对象分配的效果*p

template< class F > 
function( F f );
Run Code Online (Sandbox Code Playgroud)


mol*_*ilo 12

这是常规切片,隐藏在std::function和层下std::shared_ptr

f = *p;
Run Code Online (Sandbox Code Playgroud)

之所以有效,*p是因为它是一个带有适当对象的可调用对象operator(),这是你可以包装在一个对象中的东西之一std::function

之所以不起作用,是因为它复制了*p-这是一个Foo&,而不是一个Bar&

您上一个示例的这种改编将具有相同的行为:

Bar b;
Foo& c = b;
std::function<int(void)> f1 = c;
std::cout << f1() << std::endl;
Run Code Online (Sandbox Code Playgroud)


dar*_*une 11

切片

这是切片的情况。原因是std::function(也如另一个答案所示)的赋值运算符指出:

将* this的目标设置为可调用的f,就像通过执行function(std :: forward(f))。swap(* this);一样。除非参数类型为Args ...且返回类型为R的f可调用,否则此运算符不参与重载解析。(从C ++ 14开始)

https://zh.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
Run Code Online (Sandbox Code Playgroud)

看来您的目标是获取指向覆盖的虚函数的指针-不幸的是,没有一种简单的方法来展开虚函数查找,因为它是通过运行时查找表实现的。但是,一个简单的解决方法可能是使用lambda进行包装(正如OP也提到的那样):

f = [p]{return (*p)();};
Run Code Online (Sandbox Code Playgroud)

一个更合适的解决方案也可能只是使用reference_wrapper

f = std::ref(p);
Run Code Online (Sandbox Code Playgroud)

  • +1用于显示解决方法。但是,此代码“简化”为“ Foo f = * p;”的原因可以使用更多解释。 (3认同)