跳转至

💡 一分钟速览:std::function 底层实现拆解:类型擦除、小缓冲优化与使用示例,以及 C++23 std::move_only_function 何时取代 std::function 来装下只可移动的可调用对象。

最后更新:2026-10-13

C++ std::function 详解

最近在写C++代码时用到了is_function,然后顺便看了一下源码实现,发现了一些问题,以前咱们学习的是三个点...,那六个点......你知道是啥吗?

同样,如果有一段这样的代码:

template<typename... Args>
struct foo(Args......);

你知道是啥意思?

下面我们一起来深入细节!

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;
}

这将输出:

Is func a function? true
Is vararg_func a function? true

你学会了吗,本节完~

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 快吗?

它省掉拷贝开销和部分簿记,但两者都经过类型擦除和间接调用。如果调用点已知类型,把可调用对象做成模板形参——那才是真正的快路径。

相关主题

评论