std::function在使用时遇到的问题

1:为什么function是运行时的多态

要分清编译时和运行时,最简单的方法就是看决议(dispatch)发生在什么时候。

编译时多态(模板/重载):编译器在编译期就确定了要调用哪个函数,直接把函数地址写死在机器码里。

运行时多态(虚函数/std::function):编译器不知道具体调谁,需要在程序跑起来后,根据内存里的数据(虚表指针或函数指针)去跳转。

cpp 复制代码
// 外部调用
m_invoker->invoke(x); 

在这行代码里,m_invoker 的类型是 IInvoker*(基类指针)。编译器在编译这行代码时,根本不知道这个指针指向的是 InvokerImpl<Lambda> 还是 InvokerImpl<函数指针>。

程序运行到这一行,CPU 会读取 m_invoker 指向的内存地址,从该对象中取出虚函数表指针(vptr),再从虚表中找到 invoke 函数的实际地址,最后跳转过去。

这个"查表-跳转"的动作只能发生在程序运行期间,因为只有运行时,内存里才有了具体的对象实例。

所以,std::function 本质上就是通过虚函数机制(或等效的函数指针模拟)实现的运行时多态

2.:为什么 std::function 难以(甚至无法)被内联?

我加了 inline 关键字,或者编译器开了 O2,为什么还是内联不了?

核心原因一句话:内联(Inlining)是编译期的行为,而 std::function 的调用目标在编译期是未知的。

内联要求编译器在编译当前 .cpp 文件时,必须清清楚楚地看到被调用函数的函数体,并且确认调用的地址是确定的。

我们在代码里分析一下编译器的视角:

场景 A:直接调用 Lambda(可以内联)

cpp 复制代码
auto lambda = [](int x){ return x + 1; };
lambda(10); 

编译器看到 lambda(10),在编译期就知道要调用 Lambda::operator()。它直接把 return x + 1 这行代码插入到调用处,根本没有"函数调用"的机器码。零开销。

场景 B:通过 MyFunction 调用(难以内联)

cpp 复制代码
MyFunction f = lambda;
f(10); // 这里发生了什么?

编译器在编译 f(10) 这行时,只能看到 MyFunction::operator() 的定义:

cpp 复制代码
void operator()(int x) const {
    if (m_invoker) {
        m_invoker->invoke(x); // 这里是一个虚函数调用!
    }
}

编译器视角的分析:

它看到了 m_invoker->invoke(x),这是一个通过指针调用的虚函数。

编译器并不知道 m_invoker 指向的是谁。它可能指向 InvokerImpl<Lambda>,也可能指向 InvokerImpl<Functor>。

编译器不可能把"所有可能的子类函数体"都塞进这一个调用点,因为如果塞了 Lamba 的函数体,万一运行时传进来的是函数指针,程序就崩了。

因此,编译器只能放弃内联,生成一条标准的汇编指令:call rax + 8(从寄存器读取地址跳转)

在现代编译器中(如 GCC 的 -flto 链接时优化),有一种极其特殊的情况叫去虚拟化,想了解的可以下去了解一下

相关推荐
码爸14 小时前
线性回归算法
算法·机器学习·线性回归
Coder-magician15 小时前
《代码随想录》刷题打卡day51:图论-part09(dijistra堆优化,Bellman_ford算法)
算法·图论
Nil20815 小时前
leetcode 763划分字母区间
算法·leetcode·职场和发展
不会就选b15 小时前
算法日常・每日刷题--<动态规划>20
算法·leetcode·动态规划
别动我齐刘海15 小时前
C++ 工程化学习
java·linux·前端·c++·学习·机器学习·机器人
程序员老陆16 小时前
C++ std::promise深度解析
c++·程序设计
青少儿编程课堂16 小时前
迪杰斯特拉(Dijkstra)算法:单源最短路与赛场路线规划
c++·python·算法·bfs·信息学竞赛
不会就选b16 小时前
算法日常・每日刷题--<动态规划>19
算法·leetcode·职场和发展
沫璃染墨18 小时前
《Qt从零入门系列(十三):QSS样式表详解——从选择器与层叠规则到控件美化实战》
开发语言·css·c++·qt·ui·qt6.3·qss