💡 一分钟速览:std::function 底层实现拆解:类型擦除、小缓冲优化与使用示例,以及 C++23 std::move_only_function 何时取代 std::function 来装下只可移动的可调用对象。
最后更新:2026-10-13
C++ std::function 详解¶
最近在写C++代码时用到了is_function,然后顺便看了一下源码实现,发现了一些问题,以前咱们学习的是三个点...,那六个点......你知道是啥吗?
同样,如果有一段这样的代码:
你知道是啥意思?
下面我们一起来深入细节!
1.六个点¶
六个点我们通常叫做two ellipsis operators,我们可以将六个点进行拆分,于是得到下面三种情况
template <typename... Args> void foo1(Args......)
template <typename... Args> void foo2(Args... ...)
template <typename... Args> void foo3(Args..., ...)
这下就好看了,我们来看std::is_function的代码。
2.is_function实现细节¶
std::is_function的实现涉及到主模板和两个偏特化:
// 主模板,假设提供的类型不是函数类型
template<typename T>
struct is_function : std::false_type {};
// 偏特化,用于正常的函数类型
template<typename Ret, typename... Args>
struct is_function<Ret(Args...)> : std::true_type {};
// 偏特化,用于变参函数类型
template<typename Ret, typename... Args>
struct is_function<Ret(Args......)> : std::true_type {};
- 主模板假设提供的类型不是函数类型,并将
std::false_type作为结果。 - 第一个偏特化用于正常的函数类型,其中
Ret表示返回类型,Args...表示参数类型。如果提供的类型匹配这种函数签名,则std::true_type作为结果。 - 第二个偏特化用于变参函数类型,其中的省略号表示函数具有可变数量的参数。同样,如果提供的类型匹配这种函数签名,则
std::true_type作为结果。
3.使用示例¶
最后给出一个std::is_function的使用示例:
#include <iostream>
#include <type_traits>
int func(int x, int y) { return x + y; }
int vararg_func(int x, ...) { return x; }
int main() {
std::cout << std::boolalpha;
std::cout << "Is func a function? " << std::is_function<decltype(func)>::value << std::endl;
std::cout << "Is vararg_func a function? " << std::is_function<decltype(vararg_func)>::value << std::endl;
return 0;
}
这将输出:
你学会了吗,本节完~
C++23 std::move_only_function vs std::function¶
前面讲的类型擦除藏着一个隐性代价:std::function 要求被包装的可调用对象可拷贝构造——"包装器本身可拷贝"是它契约的一部分。在 lambda 里捕获一个 std::unique_ptr 再存进去试试,编译器直接拒绝:
auto make_sink() {
auto ptr = std::make_unique<std::vector<int>>(42);
return [ptr = std::move(ptr)] { return ptr->size(); }; // move-only closure
}
// std::function<std::size_t()> f = make_sink(); // ERROR: closure not copyable
std::move_only_function<std::size_t()> g = make_sink(); // OK since C++23
std::move_only_function(C++23,头文件
怎么选:可调用对象拷贝便宜、又要兼容老接口时,继续用 std::function;闭包独占持有资源时,换 std::move_only_function。还要记得 std::function 赋值时会拷贝闭包——捕获内容一重,这就是笔真实开销,move_only_function 正好省掉它。性能敏感的调用点,普通模板形参依然碾压两者:无擦除、无间接调用、可完全内联。
常见问题¶
std::function 和 std::move_only_function 有什么区别?¶
std::function 要求可调用对象可拷贝、赋值时拷贝;std::move_only_function(C++23)接受只可移动的可调用对象且只移动。其余行为和调用写法一致。
为什么 std::function 装不下捕获 unique_ptr 的 lambda?¶
因为 std::function 必须能拷贝存储的可调用对象,而闭包一旦捕获 unique_ptr 这种只可移动的类型,拷贝构造函数就被删除了。改用 std::move_only_function 或模板形参。
std::move_only_function 比 std::function 快吗?¶
它省掉拷贝开销和部分簿记,但两者都经过类型擦除和间接调用。如果调用点已知类型,把可调用对象做成模板形参——那才是真正的快路径。
相关主题¶
- C++ 纯虚函数与抽象类 | C++那些事
- C++ assert 断言 | C++那些事
- C++ 位域 | C++那些事
- C 语言实现 C++ 多态 | C++那些事
- C++ const 详解:常量、常量指针与指针常量的区别 | C++那些事
- C++ const 全家桶 | C++那些事
- C++ 解引用运算符 * | C++那些事
- C++ 初始化与赋值 | C++那些事
- C++ 多返回值写法 | C++那些事
- C++ mutable 关键字 | C++那些事