将函数对象分配给函数包装器后的意外行为
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::function
和std::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;
,p
是std::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);
我正在搜索应用程序中的一个错误,我终于修复了这个错误,但没有完全理解。 可以使用以下简单程序重现该行为:
#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::function
和std::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;
,p
是std::shared_ptr<Foo>
类型,那么*p
的类型是Foo&
(而不是Bar&
).即使 assignment operator of std::function
也通过引用来引用参数,但是
4) Sets the target of
*this
to the callablef
, as if by executingfunction(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);