为什么通过引用传递从 std::function 派生的对象会使程序崩溃?

Why passing an object derived from std::function by reference crashes the program?

本文关键字:对象 程序 崩溃 派生 function 引用 为什么 std      更新时间:2023-10-16

如果我通过引用将派生自 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?

std::function::operator()不是虚拟的。

class foo :public std::function<int(void)> {
public:
int operator()(){return 1;}
};

foo视为std::function时,您编写的operator()没有任何作用。 你有一个空的std::function<int()>,而不是返回 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 自动生成一个类,该类在重要方面与上面的foo类型几乎相同。 它也不会继承自std::function

C++支持多种多态性。 基于继承的多态性(很容易)不允许值类型,并且C++在值类型上蓬勃发展,因此编写std::function是为了处理值类型。


如下@T所述,

void bar(std::function<int(void)> f)

这是有效的,因为编译器在"切片"到基类和使用转换构造函数之间进行选择,并且转换构造函数是C++标准的首选。

void bar(std::function<int(void)> const& f)

这里不是首选,因为不需要进行转换,只需"视为基础",这比在规则中构造新对象的优先级更高。

在我们传递 lambda 或"非父级"foo 的情况下,"引用父级"的情况不可用,因此从我们的foo(或 lambda)创建一个临时std::functionf绑定到它。

你的重载没有被使用,常量一个(from std::function)与const std::function<int(void)>& f一起使用。 由于您不初始化std::function,它是空的,然后在调用operator ()时抛出。

按值传递时,将从函子(与任何常规函子一样)创建一个std::function,然后std::function调用存储的函子。

但不要从std::function派生,只是使用

auto f = [](){ return 1; };
bar(f);
bar(f);

您正在将类型foof传递给bar,但bar的参数const std::function<int(void)>

在这里,foo被上升为常量std::function<int(void)>

您没有在const std::function<int(void)>中重载运算符()。

所以它抛出运行时错误。