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

qft*_*qft 3 c++ c++11 std-function

如果我通过引用将从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);
}
Run Code Online (Sandbox Code Playgroud)

但是,如果函数对象通过传递,如下所示:

void bar(std::function<int(void)> f)
Run Code Online (Sandbox Code Playgroud)

程序运行正常.我在gcc,clang和visual studio上测试了这个程序,结果是一样的.导致这个bad_function_call的原因是什么?

Yak*_*ont 8

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

class foo :public std::function<int(void)> {
  public:
    int operator()(){return 1;}
};
Run Code Online (Sandbox Code Playgroud)

观看时foostd::function,在operator()你写的确实没什么.你有一个空的std::function<int()>,而不是一个返回1.

std::function基于类型擦除的多态,而不是基于继承的多态.它可以存储任何可以调用,复制和销毁的东西.您可以按值传递它,并且存储的可调用对象将随之传递.

继承它往往不是你想要做的.

class foo {
  public:
    int operator()(){return 1;}
};
Run Code Online (Sandbox Code Playgroud)

这可以转换为std::function.实际上,通过此更改,您的代码可以编译并运行.

如果没有这个改变,它更喜欢cast-to-base并将对(empty)base的引用传递std::function给参数.没有继承,它会尝试将转换foo为a std::function并成功.

当然,这foo很愚蠢.

而不是这个:

int main(){
  foo f;
  bar(f);
}
Run Code Online (Sandbox Code Playgroud)

我们做得到:

int main(){
  auto f = []{ return 1; };
  bar(f);
}
Run Code Online (Sandbox Code Playgroud)

它的工作原理也一样.(上面的lambda自动生成一个类,foo它在重要方面几乎与上面的类型相同.它不会从std::function任何一个继承.)

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


正如下面提到的@TC,

void bar(std::function<int(void)> f)
Run Code Online (Sandbox Code Playgroud)

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

void bar(std::function<int(void)> const& f)
Run Code Online (Sandbox Code Playgroud)

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

在我们传递lambda或"无代表"foo的情况下,"引用父类"的情况不可用,因此std::function从我们foo(或lambda)创建临时并f绑定它.