链接
第 3 章 语言运行期的强化 现代 C++ 教程: 高速上手 C++ 11/14/17/20 - Modern C++ Tutorial: C++ 11/14/17/20 On the Fly (changkun.de)
c++lambda 函数
C++——Lambda函数_c++拉姆达函数_勿在浮沙築高臺的博客-CSDN博客
语法:
auto func = [capture] (params) opt -> ret { func_body; };
例子:
auto func1 = [](int a) -> int { return a + 1; };
auto func2 = [](int a) { return a + 2; };
cout << func1(1) << " " << func2(2) << endl;
capture 是捕获列表
[&]
捕获外部作用域中所有变量,并作为引用在函数体内使用 (按引用捕获)
[=]
捕获外部作用域所有变量,在函数内内有个副本使用,拷贝的副本在匿名函数体内部是只读的
[=,&foo]
按值捕获外部作用域中所有变量,并按照引用捕获外部变量 foo
[bar] | 按值捕获 bar 变量,同时不捕获其他变量 |
[&bar] | 按引用捕获 bar 变量,同时不捕获其他变量 |
[this] | 捕获当前类中的 this 指针,让 lambda 表达式拥有和当前类成员函数同样的访问权限,如果已经使用了 & 或者 =, 默认添加此选项 |
#include <iostream>
#include <functional>
using namespace std;
class Test
{
public:
void output(int x, int y)
{
//auto x1 = [] {return m_number; }; // error 没有捕获m_number
auto x2 = [=] {return m_number + x + y; }; // ok
auto x3 = [&] {return m_number + x + y; }; // ok
auto x4 = [this] {return m_number; }; // ok
//auto x5 = [this] {return m_number + x + y; }; // error 捕获 this 指针,可访问类内部成员,没有捕获到变量xy
auto x6 = [this, x, y] {return m_number + x + y; }; // ok
auto x7 = [this] {return m_number++; }; // ok
}
int m_number = 100;
};
表达式捕获
#include <iostream>
#include <memory> // std::make_unique
#include <utility> // std::move
void lambda_expression_capture() {
auto important = std::make_unique<int>(1);
auto add = [v1 = 1, v2 = std::move(important)](int x, int y) -> int {
return x+y+v1+(*v2);
};
std::cout << add(3,4) << std::endl;
}
在上面的代码中,important 是一个独占指针,是不能够被 "=" 值捕获到,这时候我们可以将其转移为右值,在表达式中初始化。
函数对象包装器
Std::function
Lambda 表达式的本质是一个和函数对象类型相似的类类型(称为闭包类型)的对象(称为闭包对象), 当 Lambda 表达式的捕获列表为空时,闭包对象还能够转换为函数指针值进行传递,例如:
#include <iostream>
using foo = void(int); // 定义函数类型, using 的使用见上一节中的别名语法
void functional(foo f) { // 参数列表中定义的函数类型 foo 被视为退化后的函数指针类型 foo*
f(1); // 通过函数指针调用函数
}
int main(){
//调用 Lambda 表达式
auto f = [](int value) {
std::cout << value << std::endl;
};
f(1); // lambda 表达式调用
functional(f); // 传递闭包对象,隐式转换为 foo* 类型的函数指针值
return 0;
}
std::function
是一种通用、多态的函数封装, 函数的容器
std::bind和std::placeholder
std::placeholders::_1对一个参数
可以将部分调用参数提前绑定到函数
int foo(int a, int b, int c) {
;
}
int main() {
// 将参数1,2绑定到函数 foo 上,
// 但使用 std::placeholders::_1 来对第一个参数进行占位
auto bindFoo = std::bind(foo, std::placeholders::_1, 1,2);
// 这时调用 bindFoo 时,只需要提供第一个参数即可
bindFoo(1);
}
std::result_of<F(Args...)>::type
在 C++11 中,std::result_of
是一个模板类,它提供了一个 type
成员类型,用于表示函数调用的结果类型。
std::result_of<F(Args...)>::type
的使用方式如下:
F
:表示一个函数类型或可调用对象类型。Args...
:表示函数或可调用对象的参数类型。
std::result_of<F(Args...)>::type
会根据给定的函数类型 F
和参数类型 Args...
推断出函数调用的结果类型。
例如,假设有一个函数 int foo(double, int)
,我们可以使用 std::result_of
来获取函数调用的返回类型:
using return_type = typename std::result_of<decltype(foo)(double, int)>::type;
在这个例子中,decltype(foo)(double, int)
表示函数类型 foo
,std::result_of<decltype(foo)(double, int)>::type
将返回 int
类型,因为 foo
函数的返回类型是 int
。
std::result_of
在 C++14 中已经被弃用,取而代之的是 std::invoke_result
,它提供了更灵活和通用的类型推断功能。
std::packaged_task
可调用对象的包装器
用于将函数或可调用对象封装成一个可以异步执行的任务。它的作用是将任务的执行与任务的结果分离开来,使得任务的执行可以在一个线程中进行,而任务的结果可以在另一个线程中获取。
std::packaged_task
的使用步骤如下:
创建一个
std::packaged_task
对象,并指定任务的返回类型。例如,std::packaged_task<int()> task;
创建了一个返回类型为int
的任务对象。将任务与一个函数或可调用对象绑定。可以使用
std::bind
或 lambda 表达式来绑定任务。例如,task = std::packaged_task<int()>(std::bind(&MyClass::myFunction, myObject));
将任务绑定到MyClass
类的myFunction
成员函数。获取任务的
std::future
对象。通过调用task.get_future()
可以获取一个与任务关联的std::future
对象,用于获取任务的结果。执行任务。可以通过调用
task()
或task.operator()()
来执行任务。任务的执行可以在当前线程中进行,也可以在另一个线程中进行。获取任务的结果。通过调用
std::future
对象的get()
成员函数可以获取任务的结果。如果任务还没有执行完毕,get()
函数会阻塞当前线程,直到任务执行完毕并返回结果。
下面是一个示例代码,演示了如何使用 std::packaged_task
:
#include <iostream>
#include <future>
#include <functional>
int add(int a, int b) {
return a + b;
}
int main() {
std::packaged_task<int(int, int)> task(add); // 创建一个返回类型为 int 的任务对象,并绑定到 add 函数
std::future<int> result = task.get_future(); // 获取与任务关联的 future 对象
// 执行任务
task(2, 3);
// 获取任务的结果
int res = result.get();
std::cout << "Result: " << res << std::endl;
return 0;
}
在这个示例中,我们创建了一个返回类型为 int
的任务对象 task
,并将其绑定到 add
函数。然后,我们通过 task.get_future()
获取与任务关联的 std::future
对象 result
。接着,我们通过调用 task(2, 3)
执行任务,并通过 result.get()
获取任务的结果。最后,我们将结果打印到控制台上。
总的来说,std::packaged_task
提供了一种将函数或可调用对象封装成异步任务的方式,使得任务的执行和结果的获取可以分离开来,从而更好地利用多线程并发执行任务。
std::make_shared
std::make_shared
是一个模板函数,用于创建一个共享指针(std::shared_ptr
)对象,并在其中构造一个对象。它的作用是简化共享指针的创建过程,并提供了更安全的方式来管理动态分配的对象。
std::make_shared
的使用步骤如下:
包含
<memory>
头文件,以便使用std::make_shared
函数。调用
std::make_shared
函数,并传入要创建的对象的类型和构造函数的参数。std::make_shared
函数会返回一个共享指针(std::shared_ptr
)对象,该对象包含了指向动态分配的对象的指针,并负责管理该对象的生命周期。
下面是一个示例代码,演示了如何使用 std::make_shared
:
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass(int value) : m_value(value) {
std::cout << "Constructor called with value: " << m_value << std::endl;
}
~MyClass() {
std::cout << "Destructor called with value: " << m_value << std::endl;
}
void printValue() {
std::cout << "Value: " << m_value << std::endl;
}
private:
int m_value;
};
int main() {
std::shared_ptr<MyClass> ptr = std::make_shared<MyClass>(42); // 使用 std::make_shared 创建共享指针对象
ptr->printValue(); // 调用共享指针对象的成员函数
return 0;
}
在这个示例中,我们使用 std::make_shared
创建了一个共享指针对象 ptr
,并在其中构造了一个 MyClass
类的对象。然后,我们通过 ptr->printValue()
调用共享指针对象的成员函数。
std::make_shared
的优点是它可以一次性分配内存来存储对象和控制块(用于管理对象的引用计数等信息),从而提高内存使用效率。此外,它还提供了异常安全性,即使在构造对象时发生异常,也不会导致内存泄漏。
总的来说,std::make_shared
提供了一种方便且安全的方式来创建共享指针对象,并在其中构造对象。它是使用共享指针的推荐方式之一。
std::forward
std::forward
是一个用于完美转发(perfect forwarding)的函数模板,它用于在函数模板中将参数以原始的左值引用或右值引用形式传递给其他函数。
std::forward
的作用是根据传入的参数类型,决定将参数以左值引用还是右值引用的形式进行转发。它可以保持传递参数的值类别(value category),即将左值引用传递给左值引用形参,将右值引用传递给右值引用形参。
std::forward
的使用方式如下:
template <typename T>
void foo(T&& arg) {
bar(std::forward<T>(arg));
}
在这个示例中,我们定义了一个函数模板 foo
,它接受一个参数 arg
。在调用 bar
函数时,我们使用 std::forward<T>(arg)
将参数 arg
完美转发给 bar
函数。
std::forward
的参数类型是模板参数 T
的右值引用类型 T&&
。通过使用 std::forward<T>(arg)
,我们可以根据 arg
的值类别(左值还是右值)来决定将其以左值引用还是右值引用的形式传递给 bar
函数。
需要注意的是,std::forward
只能在函数模板中使用,并且通常与引用折叠(reference collapsing)一起使用,以实现完美转发。
通过使用 std::forward
,我们可以在泛型代码中正确地传递参数的值类别,避免不必要的拷贝或移动操作,提高代码的效率和性能。
右值引用
决了 C++ 中大量的历史遗留问题, 消除了诸如 std::vector
、std::string
之类的额外开销,
使得函数对象容器 std::function
成为了可能
将亡值
将亡值 (xvalue, expiring value),是 C++11 为了引入右值引用而提出的概念(因此在传统 C++ 中, 纯右值和右值是同一个概念),也就是即将被销毁、却能够被移动的值。
std::vector<int> foo() {
std::vector<int> temp = {1, 2, 3, 4};
return temp;
}
std::vector<int> v = foo();
这里temp刚创建就被复制给v,造成浪费,能不能直接把v=temp而不是复制。
在 C++11 之后,编译器为我们做了一些工作,此处的左值 temp
会被进行此隐式右值转换, 等价于 static_cast<std::vector<int> &&>(temp)
,进而此处的 v
会将 foo
局部返回的值进行移动。 也就是后面我们将会提到的移动语义。
右值引用和左值引用
要拿到一个将亡值,就需要用到右值引用:T &&
,其中 T
是类型。
让这个临时值的生命周期得以延长、只要变量还活着,那么将亡值将继续存活std::move
这个方法将左值参数无条件的转换为右值, 有了它我们就能够方便的获得一个右值临时对象
完美转发void reference(int& v) {
std::cout << "左值" << std::endl;
}
void reference(int&& v) {
std::cout << "右值" << std::endl;
}
template <typename T>
void pass(T&& v) {
std::cout << "普通传参:";
reference(v); // 始终调用 reference(int&)
}
int main() {
std::cout << "传递右值:" << std::endl;
pass(1); // 1是右值, 但输出是左值
std::cout << "传递左值:" << std::endl;
int l = 1;
pass(l); // l 是左值, 输出左值
return 0;
}
对于 pass(1)
来说,虽然传递的是右值,但由于 v
是一个引用,所以同时也是左值。 因此 reference(v)
会调用 reference(int&)
,输出『左值』。 而对于pass(l)
而言,l
是一个左值,为什么会成功传递给 pass(T&&)
呢?
引用坍缩规则的:在传统 C++ 中,我们不能够对一个引用类型继续进行引用, 但 C++ 由于右值引用的出现而放宽了这一做法,从而产生了引用坍缩规则,允许我们对引用进行引用, 既能左引用,又能右引用。但是却遵循如下规则:
函数形参类型 | 实参参数类型 | 推导后函数形参类型 |
---|---|---|
T& | 左引用 | T& |
T& | 右引用 | T& |
T&& | 左引用 | T& |
T&& | 右引用 | T&& |
所谓完美转发(左引用保持左引用,右引用保持右引用)
std::forward
#include <iostream>
#include <utility>
void reference(int& v) {
std::cout << "左值引用" << std::endl;
}
void reference(int&& v) {
std::cout << "右值引用" << std::endl;
}
template <typename T>
void pass(T&& v) {
std::cout << " 普通传参: ";
reference(v);
std::cout << " std::move 传参: ";
reference(std::move(v));
std::cout << " std::forward 传参: ";
reference(std::forward<T>(v));
std::cout << "static_cast<T&&> 传参: ";
reference(static_cast<T&&>(v));
}
int main() {
std::cout << "传递右值:" << std::endl;
pass(1);
std::cout << "传递左值:" << std::endl;
int v = 1;
pass(v);
return 0;
}
aa