C++11 新特性学习笔记:Lambda、std::function 与可调用对象

本文主要记录 Lambda 表达式、std::function、成员函数包装、右值引用以及 default/delete 等内容,文末会与linux线程进行联动。

核心思想:C++ 中有很多“可以被调用的东西”,C++11 提供了一套更加统一、方便的方式来表示和使用它们。


一、为什么 C++11 要引入 Lambda?

在 C++11 之前,如果想临时写一个“小函数”,通常需要单独定义:

bool Greater(int x, int y)
{
    return x > y;
}

如果只是某个地方临时使用一次,这种写法比较麻烦。

C++11 提供了 Lambda:

auto greater = [](int x, int y)
{
    return x > y;
};

之后就可以:

cout << greater(10, 5) << endl;

Lambda 的核心作用:

在需要可调用对象的地方,直接定义一个匿名的可调用对象。


二、Lambda 的基本语法

完整形式:

[capture](parameters) -> return_type
{
    function_body
};

例如:

auto add = [](int a, int b) -> int
{
    return a + b;
};

实际使用中返回值通常可以自动推导:

auto add = [](int a, int b)
{
    return a + b;
};

最常见的写法:

[](参数)
{
    函数体
}

其中:

[capture]        捕获列表
(parameters)     参数列表
-> return_type   返回值类型
{...}             函数体

三、Lambda 最重要的部分:捕获列表

Lambda 可以捕获外部变量。

3.1 值捕获

int a = 10;

auto f = [a]()
{
    cout << a << endl;
};

可以理解为 Lambda 对象内部保存了一份 a 的副本。

3.2 引用捕获

int a = 10;

auto f = [&a]()
{
    a++;
};

&a 表示捕获 a 的引用,因此修改 Lambda 中的 a 会影响外部变量。

3.3 常见捕获方式

[a, b]   // 值捕获 a、b
[&a, &b] // 引用捕获 a、b
[=]      // 默认值捕获
[&]      // 默认引用捕获
[=, &a]  // 默认值捕获,但 a 引用捕获
[&, a]   // 默认引用捕获,但 a 值捕获

其中:

[=]

表示 Lambda 中使用到的外部局部变量默认采用值捕获;

[&]

表示默认采用引用捕获。


四、局部变量和静态变量

Lambda 中普通局部变量通常需要捕获,而静态变量和全局变量不需要捕获。

例如:

int global = 100;

void Test()
{
    int local = 10;
    static int sta = 20;

    auto f = []
    {
        cout << global << endl;
        cout << sta << endl;
    };
}

这里 globalsta 都可以直接使用。

但是普通局部变量:

int local = 10;

auto f = []
{
    cout << local << endl;
};

就需要通过捕获列表捕获。


五、Lambda 的本质:匿名仿函数对象

这是理解 Lambda 最重要的知识之一。

例如:

int a = 10;

auto f = [a](int x)
{
    return x + a;
};

Lambda 看起来像一个函数,但它更接近一个匿名的类对象。

可以粗略理解为编译器生成了类似这样的类:

class Lambda
{
private:
    int a;

public:
    Lambda(int a)
        : a(a)
    {}

    int operator()(int x)
    {
        return x + a;
    }
};

于是:

auto f = [a](int x)
{
    return x + a;
};

可以粗略理解为:

Lambda f(a);

而:

f(10);

本质上就是:

f.operator()(10);

所以:

Lambda 的本质可以理解为一个匿名的仿函数对象。

这也解释了为什么 Lambda 可以保存捕获的变量——这些数据可以理解成被保存到了对象内部。


六、mutable:修改值捕获的副本

考虑:

int a = 10;

auto f = [a]()
{
    a++;
};

这不能直接修改 Lambda 内部的 a

如果希望修改值捕获的副本:

int a = 10;

auto f = [a]() mutable
{
    a++;
    cout << a << endl;
};

注意:

mutable 修改的是 Lambda 内部保存的那份副本,不会修改外面的 a

int a = 10;

auto f = [a]() mutable
{
    a++;
};

f();

cout << a << endl;  // 仍然是 10

七、引用捕获需要注意生命周期

引用捕获很方便,但必须注意生命周期。

例如:

function<int()> Test()
{
    int x = 10;

    return [&]()
    {
        return x;
    };
}

这里存在问题:

Test()
 ↓
创建 x
 ↓
Lambda 引用 x
 ↓
Test() 返回
 ↓
x 被销毁
 ↓
Lambda 仍然存在
 ↓
引用悬空

因此:

不要让引用捕获的 Lambda 活得比它引用的变量更久。

这和智能指针中的生命周期问题本质上是相通的。


八、什么是可调用对象?

std::function 之前要先理解:

可调用对象(Callable)就是可以使用 () 进行调用的对象。

常见的可调用对象:

普通函数
函数指针
Lambda
仿函数
成员函数
std::bind 生成的对象

例如:

int Add(int a, int b)
{
    return a + b;
}

函数指针:

int (*pf)(int, int) = Add;

Lambda:

auto f = [](int a, int b)
{
    return a + b;
};

仿函数:

struct Add
{
    int operator()(int a, int b)
    {
        return a + b;
    }
};

它们类型不同,但都有:

对象(参数)

这样的调用方式。


九、std::function:可调用对象的统一包装器

std::function 位于:

#include <functional>

它的核心作用:

把不同类型的可调用对象统一包装起来,并提供统一的调用方式。

例如:

function<int(int, int)> f;

可以理解为:

我要一个能够接收两个 int,最终返回一个 int 的可调用对象。

然后:

f(10, 20);

就可以统一调用。


十、function 可以包装哪些东西?

10.1 普通函数

int Add(int a, int b)
{
    return a + b;
}

function<int(int, int)> f = Add;

cout << f(10, 20) << endl;

10.2 函数指针

int Add(int a, int b)
{
    return a + b;
}

int (*pf)(int, int) = Add;

function<int(int, int)> f = pf;

cout << f(10, 20) << endl;

10.3 Lambda

function<int(int, int)> f =
    [](int a, int b)
    {
        return a + b;
    };

cout << f(10, 20) << endl;

10.4 仿函数

struct Add
{
    int operator()(int a, int b)
    {
        return a + b;
    }
};

function<int(int, int)> f = Add();

cout << f(10, 20) << endl;

因此:

普通函数
函数指针
Lambda
仿函数
     ↓
 std::function
     ↓
统一调用接口

十一、Lambda 和 function 的区别

例如:

auto f1 = [](int x)
{
    return x * 2;
};

这里 f1 是一个 Lambda 对象。

而:

function<int(int)> f2 = f1;

这里 f2 是一个 std::function 对象,它内部保存了 Lambda。

所以:

Lambda
↓
一个具体的匿名可调用对象

function
↓
一个可以统一包装各种可调用对象的包装器

因此二者不是同一个东西。


十二、std::function 的实际应用:逆波兰表达式

逆波兰表达式把运算符写在操作数后面。

例如:

1 2 + 3 *

对应普通表达式:

(1 + 2) * 3

可以利用栈计算:

1 2 +
 ↓
3

3 3 *
 ↓
9

使用 std::function,可以把不同运算统一包装。

完整代码

#include <iostream>
#include <stack>
#include <string>
#include <vector>
#include <functional>
#include <unordered_map>

using namespace std;

int main()
{
    // 将不同运算统一包装成 function<double(double, double)>
    unordered_map<string, function<double(double, double)>> operators;

    operators["+"] = [](double x, double y)
    {
        return x + y;
    };

    operators["-"] = [](double x, double y)
    {
        return x - y;
    };

    operators["*"] = [](double x, double y)
    {
        return x * y;
    };

    operators["/"] = [](double x, double y)
    {
        return x / y;
    };

    // 逆波兰表达式:1 2 + 3 *
    vector<string> tokens =
    {
        "1", "2", "+", "3", "*"
    };

    stack<double> st;

    for (const auto& token : tokens)
    {
        auto it = operators.find(token);

        if (it == operators.end())
        {
            // 不是运算符,就是数字
            st.push(stod(token));
        }
        else
        {
            // 运算符:注意先取右操作数,再取左操作数
            double right = st.top();
            st.pop();

            double left = st.top();
            st.pop();

            // 调用对应的 Lambda
            double result = it->second(left, right);

            st.push(result);
        }
    }

    cout << "result = " << st.top() << endl;

    return 0;
}

输出:

result = 9

这里最关键的是:

unordered_map<string, function<double(double, double)>> operators;

建立了:

"+" → 加法 Lambda
"-" → 减法 Lambda
"*" → 乘法 Lambda
"/" → 除法 Lambda

遇到运算符时:

it->second(left, right);

直接调用对应操作。

这样就不用写大量:

if (token == "+")
{
    ...
}
else if (token == "-")
{
    ...
}

这就是 function 的实际价值:

把不同的操作统一成可调用对象,然后通过统一接口执行。


十三、为什么学习 Linux 线程时会突然遇到 Lambda 和 function?

如果你是从 Linux C/C++ 后端的学习路线过来的,那么这一章其实不是“额外学习一堆 C++11 语法”,而是在给后面的线程代码补基础。

最典型的就是 Linux 的 pthread_create()

传统的 pthread 创建线程:

#include <iostream>
#include <pthread.h>

using namespace std;

void* ThreadRoutine(void* args)
{
    cout << "hello thread" << endl;
    return nullptr;
}

int main()
{
    pthread_t tid;

    pthread_create(&tid, nullptr, ThreadRoutine, nullptr);

    pthread_join(tid, nullptr);

    return 0;
}

这里最关键的是:

pthread_create(
    &tid,
    nullptr,
    ThreadRoutine,
    nullptr
);

pthread_create() 的第三个参数需要的是一个线程执行函数

可以把它理解成:

创建线程
   ↓
告诉系统“线程启动以后执行哪个函数”
   ↓
ThreadRoutine

这也是为什么你在学习 Linux 线程时,经常会看到:

void* (*)(void*)

这样的函数指针类型。


十四、为什么 C++ 封装线程后,Lambda 和 function 就出现了?

如果我们只写 C 风格的 pthread:

void* ThreadRoutine(void* args)
{
    // ...
}

还比较简单。

但是 C++ 更希望把“线程”和“线程要执行的任务”封装到一个类里。

例如我们可能希望这样使用:

Thread t([]()
{
    cout << "hello thread" << endl;
});

t.Start();

这里就出现了一个问题:

Lambda 是一个对象,而 pthread_create() 需要一个特定形式的函数指针,怎么把二者联系起来?

这正是 C++ 封装 Linux 线程时 Lambda、函数指针、std::function 经常一起出现的原因。


十五、用 function 保存“线程要执行的任务”

假设我们自己封装一个线程类:

class Thread
{
public:
    Thread(function<void()> func)
        : _func(func)
    {}

private:
    pthread_t _tid;
    function<void()> _func;
};

这里:

function<void()> _func;

特别值得理解。

它的意思不是:

“这是一个函数。”

而是:

这是一个可以被调用、无参数、无返回值的任务。

例如:

_func = []()
{
    cout << "hello thread" << endl;
};

也可以:

void Task()
{
    cout << "hello thread" << endl;
}

_func = Task;

还可以放一个仿函数。

所以:

Lambda
普通函数
函数指针
仿函数
      ↓
 std::function<void()>
      ↓
统一表示“一个任务”

这就是 std::function 在线程代码中的实际意义。


十六、pthread_create 和 C++ 类之间的矛盾

继续往下看。

假设我们写:

class Thread
{
public:
    void Run()
    {
        cout << "hello thread" << endl;
    }
};

我们可能很自然地想:

pthread_create(&_tid, nullptr, Run, this);

但是这是不行的。

为什么?

因为:

Run

成员函数,而 pthread_create() 需要的是类似:

void* (*)(void*)

的普通函数指针。

成员函数和普通函数不是一回事。

成员函数背后隐含了一个:

this 指针

所以不能直接当成普通函数指针传进去。


十七、经典解决方案:static + this

Linux C++ 线程封装中经常看到这种写法:

class Thread
{
public:
    void Start()
    {
        pthread_create(&_tid, nullptr, ThreadRoutine, this);
    }

private:
    static void* ThreadRoutine(void* args)
    {
        Thread* self = static_cast<Thread*>(args);

        self->Run();

        return nullptr;
    }

    void Run()
    {
        cout << "hello thread" << endl;
    }

private:
    pthread_t _tid;
};

这里是一个非常重要的设计。

创建线程时:

pthread_create(&_tid, nullptr, ThreadRoutine, this);

我们把:

this

作为第四个参数传进去。

于是:

当前 Thread 对象
       ↓
      this
       ↓
pthread_create()
       ↓
线程启动
       ↓
ThreadRoutine(void* args)
       ↓
args 接收到 this
       ↓
还原成 Thread*
       ↓
self->Run()

这里你之前学到的:

static_cast<Thread*>(args)

就又出现了。

它的作用就是:

void* 还原成原来的 Thread*


十八、为什么 ThreadRoutine 要写成 static?

这里也非常容易困惑。

如果写:

class Thread
{
    void* ThreadRoutine(void* args);
};

那么它实际上是一个成员函数。

成员函数调用时需要一个对象,也就是隐含的:

this

但是 pthread 根本不知道你的 C++ 对象是什么。

它只知道:

void* (*)(void*)

所以这里通常使用:

static void* ThreadRoutine(void* args)

static 成员函数没有隐藏的 this 参数,因此它的函数形式可以匹配 pthread 需要的普通函数指针。

然后我们再通过:

void* args

this 传回来。

这是一种非常经典的:

C 接口 + C++ 对象封装

的桥接方式。


十九、把 function 和 pthread 完整串起来

现在可以进一步把线程类写成:

class Thread
{
public:
    Thread(function<void()> func)
        : _func(func)
    {}

    void Start()
    {
        pthread_create(&_tid, nullptr, ThreadRoutine, this);
    }

    void Join()
    {
        pthread_join(_tid, nullptr);
    }

private:
    static void* ThreadRoutine(void* args)
    {
        Thread* self = static_cast<Thread*>(args);

        // 真正执行线程任务
        self->_func();

        return nullptr;
    }

private:
    pthread_t _tid;
    function<void()> _func;
};

使用:

int main()
{
    Thread t([]()
    {
        cout << "hello thread" << endl;
    });

    t.Start();
    t.Join();

    return 0;
}

现在整条链路就非常清楚了:

Lambda
  ↓
创建一个任务
  ↓
function<void()>
  ↓
保存任务
  ↓
Thread 对象
  ↓
pthread_create()
  ↓
static ThreadRoutine()
  ↓
通过 this 找回 Thread 对象
  ↓
_func()
  ↓
执行 Lambda

这就是为什么你在 Linux 线程代码里会突然遇到:

Lambda
function
static
this
void*
static_cast

它们并不是毫无关系的一堆知识点,而是在共同完成:

“把 C++ 的一个任务交给 Linux 线程执行”这件事。


二十、Lambda 为什么有时候可以直接传给 pthread?

这里还有一个很重要的细节。

例如:

pthread_create(
    &tid,
    nullptr,
    [](void* args) -> void*
    {
        cout << "hello" << endl;
        return nullptr;
    },
    nullptr
);

这个 Lambda 没有捕获外部变量

这种 Lambda 可以转换成对应形式的普通函数指针,因此可以作为 pthread_create() 的第三个参数。

但是如果:

int x = 10;

auto func = [x](void* args) -> void*
{
    cout << x << endl;
    return nullptr;
};

这个 Lambda 捕获了 x

此时它本质上已经是一个需要保存状态的对象:

Lambda对象
 ├── 保存 x
 └── operator()()

它不能简单地转换成普通函数指针。

这也是为什么实际 C++ 线程封装中,经常不是直接把一个复杂 Lambda 塞进 pthread,而是:

Lambda
 ↓
function
 ↓
保存任务
 ↓
Thread对象
 ↓
pthread_create
 ↓
静态入口函数
 ↓
调用function

这样就把“Linux 线程入口”和“真正执行的 C++ 任务”分开了。


二十一、C++11 的 std::thread 又是什么?

C++11 不仅提供了 Lambda 和 std::function,还提供了:

#include <thread>

于是可以直接:

#include <iostream>
#include <thread>

using namespace std;

int main()
{
    thread t([]()
    {
        cout << "hello thread" << endl;
    });

    t.join();

    return 0;
}

相比 pthread:

pthread_create(...)

std::thread 更贴近 C++ 的对象模型。

它可以直接接受各种可调用对象:

普通函数
Lambda
仿函数
成员函数

例如:

void Task()
{
    cout << "hello" << endl;
}

thread t1(Task);

thread t2([]()
{
    cout << "hello" << endl;
});

因此可以粗略理解:

pthread
   ↓
Linux/POSIX 原生线程接口
   ↓
偏 C 风格

std::thread
   ↓
C++11 对线程的封装
   ↓
更方便地使用各种可调用对象

当然,在 Linux 后端学习中仍然非常有必要理解 pthread,因为它能帮助我们理解线程创建、线程入口、线程参数等更底层的机制。


二十二、为什么线程池特别喜欢 function<void()>?

当你继续学习线程池时,很可能看到:

queue<function<void()>> _tasks;

第一次看可能会觉得:

“队列里面为什么放函数?”

其实这里的“函数”更准确地说是:

一个个可以被执行的任务对象。

例如:

_tasks.push([]()
{
    cout << "task 1" << endl;
});

_tasks.push([]()
{
    cout << "task 2" << endl;
});

线程从任务队列中取任务:

function<void()> task;

task = _tasks.front();
_tasks.pop();

task();

整个过程:

                任务生产者
                    ↓
        ┌────────────────────┐
        │ queue<function<>>  │
        └────────────────────┘
                    ↓
                 取任务
                    ↓
              function<void()>
                    ↓
                  task()
                    ↓
                工作线程执行

这就是线程池最核心的思想之一:

线程负责“执行”,任务负责“描述要执行什么”。

而 Lambda + std::function 恰好让“任务”变得非常容易表示。

例如:

function<void()> task = []()
{
    cout << "计算任务" << endl;
};

甚至可以捕获参数:

int x = 10;
int y = 20;

function<void()> task = [x, y]()
{
    cout << x + y << endl;
};

这时任务对象内部就保存了:

x
y
以及 operator()

所以线程拿到任务以后:

task();

就能直接执行。


二十三、从 Lambda 一路走到线程池

现在可以把你目前学习的内容完整串起来:

                C++11
                  │
        ┌─────────┴─────────┐
        ↓                   ↓
      Lambda            std::function
        │                   │
        │             统一包装任务
        │                   │
        └─────────┬─────────┘
                  ↓
             function<void()>
                  ↓
                一个任务
                  ↓
              任务队列
                  ↓
             工作线程取任务
                  ↓
                task()
                  ↓
              执行任务

再和 Linux pthread 联系起来:

C++任务
  ↓
function<void()>
  ↓
Thread对象
  ↓
pthread_create()
  ↓
Linux线程
  ↓
线程入口函数
  ↓
调用function
  ↓
真正执行任务

这时候你会发现,之前课堂上那些看起来“东一个、西一个”的 C++11 特性,其实都在为后面的 Linux/C++ 编程服务。


二十四、Lambda、function 和线程:一句话理解

如果只想留下一个最核心的印象,可以记住:

Lambda 负责方便地创建任务,std::function 负责统一保存任务,而线程负责执行任务。

再进一步:

Lambda
→ 我想让线程干什么?

function<void()>
→ 把“这件事”保存下来

pthread / std::thread
→ 找一个线程去执行它

这样以后再看到:

function<void()> task;

就不要把它理解成一个复杂的 C++11 语法,而应该直接想到:

“这里保存的是一个以后可以执行的任务。”


二十六、function 包装成员函数

例如:

class Plus
{
public:
    double plusd(double x, double y)
    {
        return x + y;
    }
};

成员函数不能像普通函数一样直接调用,因为它属于某个对象。

获取成员函数地址:

&Plus::plusd

注意:

获取成员函数地址时,需要加上类名和 &


二十七、第一种方式:使用对象指针

function<double(Plus*, double, double)> f1 = &Plus::plusd;

Plus p1;

cout << f1(&p1, 1.1, 1.11) << endl;

这里 Plus* 表示调用成员函数时需要传入一个 Plus 对象指针。


二十八、第二种方式:直接使用对象

function<double(Plus, double, double)> f2 = &Plus::plusd;

cout << f2(Plus(), 1.1, 1.11) << endl;

这里直接构造了一个临时 Plus 对象:

Plus()

所以不需要自己创建对象。


二十九、第三种方式:使用右值引用

function<double(Plus&&, double, double)> f3 = &Plus::plusd;

cout << f3(Plus(), 1.1, 1.11) << endl;

因为:

Plus()

本身就是右值,所以可以传给:

Plus&&

如果已有对象:

Plus p1;

还可以:

f3(std::move(p1), 1.1, 1.11);

注意:

std::move() 本身不会真正执行移动,它主要是把一个对象转换成右值表达式,从而允许匹配右值引用。

真正的移动行为需要类型提供移动构造、移动赋值等操作。


三十、成员函数指针与 .* / ->*

成员函数指针可以写成:

double (Plus::*pf)(double, double) = &Plus::plusd;

如果有对象:

Plus p;

可以:

(p.*pf)(1.1, 2.2);

.* 表示:

通过对象 + 成员函数指针调用成员函数。

如果是对象指针:

Plus* p = new Plus;

则使用:

(p->*pf)(1.1, 2.2);

因此:

对象 + 成员函数指针
        ↓
       .*

对象指针 + 成员函数指针
        ↓
       ->*

std::function 帮助我们把这些调用细节包装起来。


三十一、std::function 背后的思想:类型擦除

考虑:

function<int(int)> f;

它可以保存:

int func(int);

也可以保存:

struct Test
{
    int operator()(int x)
    {
        return x * 2;
    }
};

还可以保存:

[](int x)
{
    return x * 2;
}

这些东西实际上都是不同的类型。

function 能够统一保存它们,背后涉及一个重要概念:

类型擦除(Type Erasure)

简单理解:

不同类型的可调用对象
        ↓
      function
        ↓
隐藏具体类型
        ↓
统一通过 operator() 调用

目前只需要理解这个思想,不必深入源码。


三十二、default 和 delete

C++11 引入了:

= default
= delete

19.1 default

class A
{
public:
    A() = default;
};

意思是:

告诉编译器使用默认生成的实现。

19.2 delete

class A
{
public:
    A(const A&) = delete;
};

意思是:

明确禁止调用这个函数。

例如上面的代码就是禁止拷贝构造。

这和 unique_ptr 可以联系起来:

unique_ptr(const unique_ptr&) = delete;

所以 unique_ptr 不能拷贝,本质上就是类主动删除了拷贝构造函数。


三十三、这一部分知识之间的关系

可以把它们串成:

                  可调用对象
                      │
        ┌─────────────┼─────────────┐
        ↓             ↓             ↓
      普通函数       仿函数         Lambda
        │             │             │
        └─────────────┼─────────────┘
                      ↓
                std::function
                      ↓
                 统一包装
                      ↓
                 统一调用

其中:

Lambda 是一种方便创建匿名可调用对象的语法,本质可以理解成匿名仿函数。

而:

std::function 负责把不同类型的可调用对象统一包装起来。


三十四、这一部分最值得记住的内容

Lambda

Lambda = 匿名函数对象

重点:

[]
[=]
[&]
[a]
[&a]
mutable

以及:

Lambda 本质是一个重载了 operator() 的匿名对象。

function

std::function = 可调用对象包装器

可以包装:

普通函数
函数指针
Lambda
仿函数
成员函数

核心价值:

统一不同类型可调用对象的调用方式。

成员函数

&Class::func

获取成员函数指针。

对象 .* 成员函数指针
对象指针 ->* 成员函数指针

右值引用

std::move(x)

主要作用:

把 x 转换成右值表达式。

它本身不等于“执行移动”。

default / delete

= default

→ 使用默认实现。

= delete

→ 禁止使用该函数。


三十五、和后面的线程联系起来

这些知识看起来比较零散:

Lambda
function
仿函数
成员函数指针
右值引用

但后面学习线程时会逐渐串起来。

例如:

thread t([]()
{
    cout << "hello thread" << endl;
});

这里直接使用了 Lambda。

线程池中又经常会看到:

function<void()> task;

可以把各种不同的任务统一保存起来:

任务队列
   ↓
function<void()>
   ↓
保存各种不同的任务
   ↓
线程取出任务
   ↓
task()

这时候就会发现:

Lambda + function 并不是为了增加语法复杂度,而是为了让“任务”这种东西能够被统一表示、保存和调用。


总结

这次回来补 C++11,真正的目的其实不是把所有 C++11 新语法全部学一遍,而是解决一个非常实际的问题:

为什么学习 Linux 线程的时候,会突然遇到 Lambda、仿函数、std::function、成员函数指针、右值引用这些东西?

现在可以把它们串起来:

Lambda
  ↓
快速创建一个可调用对象

仿函数
  ↓
理解 Lambda 的底层思想

std::function
  ↓
统一包装各种可调用对象

function<void()>
  ↓
非常适合表示“一个待执行的任务”

pthread / std::thread
  ↓
让线程去执行任务

任务队列
  ↓
保存大量 function<void()>

线程池
  ↓
工作线程不断取出任务并执行

因此,如果从 Linux C/C++ 后端的角度来看:

Lambda 负责方便地描述“要做什么”,std::function 负责把这个任务保存下来,线程负责把任务执行起来。

而成员函数指针、staticthisvoid*static_cast 等内容,则是在解决:

如何把 C 风格的 Linux pthread 接口和 C++ 的对象模型连接起来。

最后把几个核心知识点压缩成几句话:

Lambda 是一种方便创建匿名可调用对象的语法,本质上可以理解为仿函数。

std::function 是可调用对象的通用包装器,可以把普通函数、函数指针、Lambda、仿函数等统一起来。

成员函数属于对象,因此使用成员函数指针时需要结合对象或对象指针进行调用。

std::move 主要负责把对象转换成右值表达式,而真正的移动由移动构造/移动赋值等函数完成。

在 Linux/C++ 后端中,Lambda + std::function 最重要的应用之一,就是把“任务”从线程本身中抽离出来,让线程可以统一执行各种不同的任务。

更多推荐