Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

Sidebar Navigation

← C++ 编程 / C++ Programming

现代 C++ / Modern C++

1. 现代 C++ 总览:不是特性清单,而是表达能力 / Modern C++ Overview

2. 移动、转发与值类别:std::move 为什么不移动 / Move, Forward, and Value Categories

3. 类型推导、初始化与 constexpr / Type Deduction, Initialization, and constexpr

4. 模板、实例化与 SFINAE / Templates, Instantiation, and SFINAE

5. concept 与约束:把模板要求写在接口上 / Concepts and Constraints

6. 异常、RAII 与 noexcept:失败路径也要清理 / Exceptions, RAII, and noexcept

7. lambda、捕获与生命周期:回调不是免费午餐 / Lambdas, Captures, and Lifetime

8. callable wrapper 与 std::invoke:怎样保存和调用行为 / Callable Wrappers and std::invoke

9. 词汇类型与错误建模:让接口自己说话 / Vocabulary Types and Error Modeling

10. 协程与异步抽象:先理解状态机 / Coroutines and Async Abstractions

本页目录

callable wrapper 与 std::invoke:怎样保存和调用行为 / Callable Wrappers and std::invoke ​

callable 不是一个具体类型。

它是一类“能被调用”的东西。现代 C++ 里,行为也可以像数据一样被传递、保存和组合。

1. 什么东西能被调用 ​

普通函数:

cpp
void hello() {}

hello();
1
2
3

函数指针:

cpp
void (*fp)() = &hello;
fp();
1
2

lambda:

cpp
auto f = [] {
    // do something
};

f();
1
2
3
4
5

函数对象:

cpp
struct Printer {
    void operator()() const {
        // do something
    }
};

Printer printer;
printer();
1
2
3
4
5
6
7
8

它们都能写成类似:

cpp
callable();
1

但类型完全不同。

text
function
function pointer
lambda closure type
function object type
member function pointer
1
2
3
4
5

callable 这个词就是把这些“能被调用的东西”放在一起讨论。

2. 用模板接收 callable ​

最直接的方式是模板:

cpp
template <class F>
void run(F f) {
    f();
}
1
2
3
4

调用:

cpp
run([] {
    // work
});
1
2
3

优点:

text
保留 callable 的具体类型
编译器容易内联
没有 std::function 的类型擦除成本
可以接收 move-only callable
1
2
3
4

缺点:

text
实现通常要放在头文件
每种 F 都可能实例化一份代码
错误信息可能更模板化
不能轻易把不同 callable 放进同一个变量
1
2
3
4

模板适合“调用点和 callable 类型都在编译期已知”的场景。

3. std::function:把不同 callable 装进同一种类型 ​

如果你需要保存一个回调:

cpp
#include <functional>

class Button {
public:
    void on_click(std::function<void()> callback) {
        callback_ = std::move(callback);
    }

    void click() {
        if (callback_) {
            callback_();
        }
    }

private:
    std::function<void()> callback_;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

std::function<void()> 表示:

text
可以保存任何能按 void() 方式调用的 callable。
1

它可以保存:

cpp
Button button;

button.on_click([] {
    // lambda
});
1
2
3
4
5

也可以保存函数指针、函数对象等。

图:

text
lambda / function pointer / functor
  |
  v
std::function<void()>
  |
  v
uniform call: callback()
1
2
3
4
5
6
7

这叫类型擦除。

“擦除”不是说类型真的不存在。

而是说使用者不再需要知道内部保存的具体 callable 类型。

4. std::function 的代价 ​

std::function 很方便,但有成本:

text
可能发生动态分配
调用可能是间接调用
通常要求 callable 可复制
会隐藏具体类型,影响内联机会
空 std::function 被调用会抛 bad_function_call
1
2
3
4
5

所以选择时不要一律写 std::function。

如果只是立即调用一次:

cpp
template <class F>
void with_lock(F&& f) {
    // lock
    f();
    // unlock
}
1
2
3
4
5
6

模板通常更合适。

如果要把回调存到对象里,稍后调用,且需要统一类型:

cpp
std::function<void()> callback_;
1

就更自然。

5. 成员函数指针为什么特殊 ​

普通函数指针可以直接调用。

成员函数指针需要对象。

cpp
struct Widget {
    void hello() {}
};

auto ptr = &Widget::hello;
Widget w;

(w.*ptr)();
1
2
3
4
5
6
7
8

语法看起来很怪。

原因是成员函数不是独立函数。

它需要一个对象作为隐含的 this。

text
member function pointer
  |
  v
needs object
  |
  v
call on that object
1
2
3
4
5
6
7

如果是对象指针:

cpp
Widget* p = &w;
(p->*ptr)();
1
2

这也是成员函数指针语法难看的原因。

6. std::invoke 统一调用 ​

std::invoke 可以统一处理多种 callable:

cpp
#include <functional>

std::invoke([] { });
std::invoke(&Widget::hello, w);
std::invoke(&Widget::hello, &w);
1
2
3
4
5

它大致表达:

text
不管这是普通函数、lambda、函数对象、成员函数指针,
请按正确规则调用它。
1
2

标准库里很多泛型设施会基于 invoke 的思想。

因为泛型代码不想手写一堆分支判断 callable 类型。

7. move-only callable ​

std::function 在 C++17/20 中通常要求保存的 callable 可复制。

但有些 lambda 捕获了 unique_ptr:

cpp
#include <memory>

auto ptr = std::make_unique<int>(42);

auto task = [p = std::move(ptr)] {
    return *p;
};
1
2
3
4
5
6
7

这个 lambda 是 move-only 的。

因为它内部拥有 unique_ptr。

这类 callable 不能放进传统 std::function。

C++23 有 std::move_only_function。

在 C++17/20 项目里,可能需要自定义包装器或改变设计。

这再次说明:callable 也会涉及所有权。

8. 本篇总结 ​

选择 callable 表达方式时问:

text
只是立即调用,还是要保存?
是否需要保存不同具体类型?
是否需要复制 callable?
callable 是否 move-only?
性能是否敏感?
是否涉及成员函数指针?
1
2
3
4
5
6

大致选择:

text
模板参数 F
  编译期已知,性能好,适合立即调用

std::function
  运行期保存不同 callable,接口统一,但有类型擦除成本

std::invoke
  泛型代码中统一调用各种 callable

move_only_function 或自定义包装
  保存 move-only callable
1
2
3
4
5
6
7
8
9
10
11

callable 的核心不是语法。

它是“行为作为对象”之后,类型、生命周期和所有权如何表达。

最后更新于:

Pager
上一篇7. lambda、捕获与生命周期:回调不是免费午餐 / Lambdas, Captures, and Lifetime
下一篇9. 词汇类型与错误建模:让接口自己说话 / Vocabulary Types and Error Modeling

持续记录,持续成长

Copyright © Tidenflow