将函数对象分配给函数包装器后的意外行为

Unexpected behavior after assignment of function object to function wrapper

我正在搜索应用程序中的一个错误,我终于修复了这个错误,但没有完全理解。 可以使用以下简单程序重现该行为:

#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 被允许并且不会导致错误。

我不要求解决方法,因为我用 lambda 修复了它。
我的问题是,当我执行 f = *p 时发生了什么,为什么输出是 1 而不是 2?

我用 gcc (MinGW) 和 Visual Studio 2019 重现了这个问题。
此外,我想提一下

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

又是2

指针p的静态类型是Foo

所以在这个声明中

f = *p;

左操作数 *p 的类型为 Foo,即存在切片。

这是规则切片,隐藏在std::functionstd::shared_ptr层下。

f = *p;

是有效的,因为 *p 是一个具有适当 operator() 的可调用对象,这是您可以包装在 std::function.

中的内容之一

它不起作用的原因是它复制了 *p – 那是 Foo&,而不是 Bar&

你上一个例子的这种改编将表现相同:

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

对象切片发生在这里。

给出的点是f = *p;pstd::shared_ptr<Foo>类型,那么*p的类型是Foo&(而不是Bar&).即使 assignment operator of std::function 也通过引用来引用参数,但是

4) Sets the target of *this to the callable f, as if by executing function(std::forward<F>(f)).swap(*this);.

请注意,上面的 F 也被推断为 Foo&。并且 constructor of std::function 按值获取参数,对象切片发生,效果变为 f 是从 Foo 类型的对象分配的,该类型的对象是从 *p 切片复制的。

template< class F > 
function( F f );

切片

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

Sets the target of *this to the callable f, as if by executing function(std::forward(f)).swap(*this);. This operator does not participate in overload resolution unless f is Callable for argument types Args... and return type R. (since 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

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

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

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

f = std::ref(p);