为什么通过引用传递从 std::function 派生的对象会使程序崩溃?
Why passing an object derived from std::function by reference crashes the program?
如果我通过引用将派生自 std::function 的对象传递给另一个函数,程序总是会在运行时崩溃并出现 bad_function_call 错误。如下代码所示:
#include<functional>
#include<iostream>
class foo :public std::function<int(void)> {
public:
int operator()(){return 1;}
};
void bar(const std::function<int(void)>& f) {std::cout << f() << std::endl;}
int main(){
foo f;
bar(f);
}
但是,如果函数对象是按值传递的,就像这样:
void bar(std::function<int(void)> f)
程序运行正常。我已经在 gcc、clang 和 visual studio 上测试了该程序,结果是一样的。是什么导致了这个 bad_function_call?
你的重载没有被使用,const 一个 (from std::function
) 与 const std::function<int(void)>& f
一起使用。
由于您没有初始化 std::function
,它是空的,然后在调用 operator ()
时抛出。
当您按值传递时,std::function
是从您的仿函数创建的(与任何常规仿函数一样),然后 std::function
调用您的存储仿函数。
但不要从 std::function
派生,只需使用
auto f = [](){ return 1; };
bar(f);
std::function::operator()
不是虚拟的。
class foo :public std::function<int(void)> {
public:
int operator()(){return 1;}
};
将 foo
视为 std::function
时,您编写的 operator()
没有任何作用。你有一个空 std::function<int()>
,而不是 returns 1.
std::function
执行基于类型擦除的多态性,而不是基于继承的多态性。它可以存储任何它可以调用、复制和销毁的东西。您可以按值传递它,存储的可调用对象将随之传递。
继承它通常不是你想做的。
class foo {
public:
int operator()(){return 1;}
};
这可以转换成std::function
。事实上,通过此更改,您的代码可以编译并运行。
如果不进行此更改,它更喜欢转换为基数并将对(空)基数 std::function
的引用传递给参数。如果没有继承,它会尝试将 foo
转换为 std::function
并成功。
当然,这个 foo
很傻。
而不是这个:
int main(){
foo f;
bar(f);
}
我们可以做到:
int main(){
auto f = []{ return 1; };
bar(f);
}
而且效果也很好。 (上面的 lambda 自动生成一个 class,它在重要方面几乎与上面的 foo
类型相同。它也没有继承自 std::function
。)
C++ 支持不止一种多态性。基于继承的多态性不会(轻易地)允许值类型,而 C++ 在值类型上蓬勃发展,因此 std::function
被编写为与值类型一起工作。
作为@T.C。如下所述,
void bar(std::function<int(void)> f)
这是可行的,因为编译器在 "slicing" 和基础 class 之间进行选择,并使用转换构造函数,而 C++ 标准首选转换构造函数。
void bar(std::function<int(void)> const& f)
这里不是首选,因为不需要做转换,只是"treat as base",比在规则中构造一个新对象优先级更高
在我们传递 lambda 或 "unparented" foo 的情况下,"take a reference to parent" 情况不可用,因此临时 std::function
从我们的 foo
(或 lambda)和 f
绑定到它。
bar(f);
您正在将 foo
类型的 f
传递给 bar
,但是 bar
的参数是 const std::function<int(void)>
这里 foo
被升级为 const std::function<int(void)>
。
您没有在 const std::function<int(void)>
中重载 operator()。
所以它抛出运行时错误。
如果我通过引用将派生自 std::function 的对象传递给另一个函数,程序总是会在运行时崩溃并出现 bad_function_call 错误。如下代码所示:
#include<functional>
#include<iostream>
class foo :public std::function<int(void)> {
public:
int operator()(){return 1;}
};
void bar(const std::function<int(void)>& f) {std::cout << f() << std::endl;}
int main(){
foo f;
bar(f);
}
但是,如果函数对象是按值传递的,就像这样:
void bar(std::function<int(void)> f)
程序运行正常。我已经在 gcc、clang 和 visual studio 上测试了该程序,结果是一样的。是什么导致了这个 bad_function_call?
你的重载没有被使用,const 一个 (from std::function
) 与 const std::function<int(void)>& f
一起使用。
由于您没有初始化 std::function
,它是空的,然后在调用 operator ()
时抛出。
当您按值传递时,std::function
是从您的仿函数创建的(与任何常规仿函数一样),然后 std::function
调用您的存储仿函数。
但不要从 std::function
派生,只需使用
auto f = [](){ return 1; };
bar(f);
std::function::operator()
不是虚拟的。
class foo :public std::function<int(void)> {
public:
int operator()(){return 1;}
};
将 foo
视为 std::function
时,您编写的 operator()
没有任何作用。你有一个空 std::function<int()>
,而不是 returns 1.
std::function
执行基于类型擦除的多态性,而不是基于继承的多态性。它可以存储任何它可以调用、复制和销毁的东西。您可以按值传递它,存储的可调用对象将随之传递。
继承它通常不是你想做的。
class foo {
public:
int operator()(){return 1;}
};
这可以转换成std::function
。事实上,通过此更改,您的代码可以编译并运行。
如果不进行此更改,它更喜欢转换为基数并将对(空)基数 std::function
的引用传递给参数。如果没有继承,它会尝试将 foo
转换为 std::function
并成功。
当然,这个 foo
很傻。
而不是这个:
int main(){
foo f;
bar(f);
}
我们可以做到:
int main(){
auto f = []{ return 1; };
bar(f);
}
而且效果也很好。 (上面的 lambda 自动生成一个 class,它在重要方面几乎与上面的 foo
类型相同。它也没有继承自 std::function
。)
C++ 支持不止一种多态性。基于继承的多态性不会(轻易地)允许值类型,而 C++ 在值类型上蓬勃发展,因此 std::function
被编写为与值类型一起工作。
作为@T.C。如下所述,
void bar(std::function<int(void)> f)
这是可行的,因为编译器在 "slicing" 和基础 class 之间进行选择,并使用转换构造函数,而 C++ 标准首选转换构造函数。
void bar(std::function<int(void)> const& f)
这里不是首选,因为不需要做转换,只是"treat as base",比在规则中构造一个新对象优先级更高
在我们传递 lambda 或 "unparented" foo 的情况下,"take a reference to parent" 情况不可用,因此临时 std::function
从我们的 foo
(或 lambda)和 f
绑定到它。
bar(f);
您正在将 foo
类型的 f
传递给 bar
,但是 bar
的参数是 const std::function<int(void)>
这里 foo
被升级为 const std::function<int(void)>
。
您没有在 const std::function<int(void)>
中重载 operator()。
所以它抛出运行时错误。