C++学习笔记|C++11常用特性


文章目录

  • C++11
    • 什么是C++11
    • 新特性
      • {}初始化
        • 简单实例
        • {}的类型
      • auto
        • 简单实例
      • decltype
      • nullptr
      • 范围for
      • 左右值引用
        • 左值和右值是什么
        • 左值和右值的区分
        • 左右值引用
        • 左右值引用的作用
        • 移动语义
          • 场景
          • move
        • 完美转发
      • default、delete、final、override
      • 可变参数模板
        • 递归展开
        • 逗号表达式展开
        • emplace_back和push_back
      • STL增加的容器
      • lambda表达式
        • lambda参数解释
          • 捕捉列表
          • 参数列表
          • mutable
          • ->返回值类型
          • {函数体}
          • 关于省略
      • 包装器
        • 用法
        • bind
          • 绑定普通函数
          • 绑定成员函数
  • 总结

C++11 什么是C++11 C++11标准是 ISO/IEC 14882:2011 - Information technology – Programming languages – C++ 的简称 [1] 。
C++11标准由国际标准化组织(ISO)和国际电工委员会(IEC)旗下的C++标准委员会(ISO/IEC JTC1/SC22/WG21)于2011年8月12日公布 [2] ,并于2011年9月出版。2012年2月28日的国际标准草案(N3376)是最接近于C++11标准的草案(仅编辑上的修正)。此次标准为C++98发布后13年来第一次重大修正。–百度百科
翻译一下,2011年发布的C++标准,对C++98的一次重大修正。
新特性 下面的新特性有{}初始化,auto,declytype,nullptr,范围for,STL的变化,左右值引用,lambda表达式,包装器
智能指针和线程库之后补充。
{}初始化
C++11提供了{}初始化的方法。
简单实例
int main() { //{}的简单使用 using namespace std; int arr1[] = { 1,2,3,4,5 }; int arr2[]{ 1,2,3,4,5 }; pair(1, 1); pair{1, 1}; pair* pr = new pair[2]{ {"sort",1} ,{"good",2} }; return 0; }

C++学习笔记|C++11常用特性
文章图片

{}的类型 {}在C++11里面是一个类型,initializer_list,initializer_list - C++ Reference (cplusplus.com),也叫初始化列表。
template class initializer_list;

C++学习笔记|C++11常用特性
文章图片

initializer_list提供了迭代器,所以可以遍历列表内的元素。
C++学习笔记|C++11常用特性
文章图片

以vector为例,里面提供了这种类型的构造函数,所以vector < int > v{1,2,3}这种用法就是在调用构造函数
我们可以想想可以怎么实现这个功能,遍历initializer_list的元素,挨个赋值给vector容器即可。(没看库里怎么实现的,不过是阐述一种可行的思路
C++学习笔记|C++11常用特性
文章图片

STL库中的大多容器都增加了initializer_list相关的构造函数
用初始化列表可以加强类型安全。比如int a=1.2,最后a存储的是1,这就是类型收窄。如果初始化列表的使用导致了类型收窄那编译器就会报错,由此也加强了类型安全,所以建议使用初始化列表初始化。
auto
C++98是有auto的,不过在C++11被废弃了,我们现在的int a,就是C++98的auto int a
auto是一个存储类型的说明符,作用是推导类型。
简单实例
int main() { auto a = 0; auto b = 1L; auto c = 1.1; std::vector::string>::iterator it; auto d = it; return 0; } //如果有一个类型很长比如std::vectorstring_v; 还要去写它的迭代器就更麻烦了,这种情况下就可以用auto去代替

C++学习笔记|C++11常用特性
文章图片

auto一定程度的降低了程序的可读性,并且我们不应该滥用auto.
滥用auto看起来更方便,实际上会造成很多问题,比如难以维护,再比如
string str="ccl"; auto s=str; s.size(); //上面的代码是合理的,但是当我们有一天将string改为const char*时,虽然auto方面没有问题,但是依赖于string类型的代码全部出问题了。

decltype
将变量的类型指定为一个表达式的类型
C++学习笔记|C++11常用特性
文章图片

typeid(z).name返回的是一个字符串,所以肯定不能拿其返回值当类型了
nullptr
nullptr是一个对象,nullptr的类型是nullptr_t,并不等同于((void*)0),nullptr可以隐式的转为任意类型的指针,同时nullptr不能取地址。此外由于nullptr有类型所以是可以捕捉异常的。
C++学习笔记|C++11常用特性
文章图片

//网上找的一种可能的实现 struct nullptr_t { void operator&() const = delete; template inline operator T*() const { return 0; } template inline operator T C::*() const { return 0; } }; nullptr_t nullptr;

范围for
遍历容器内的所有元素,如果要修改需要加引用,本质上就是迭代器的遍历。
C++学习笔记|C++11常用特性
文章图片

VS监视小技巧
C++学习笔记|C++11常用特性
文章图片

左右值引用
左值和右值是什么 左值就是可以取地址的,右值就是不可以取地址的。
我们经常可以知道一些关于左右值的判断,而很少听到其真正的定义的一个原因是很难归纳,而且就算归纳了,也需要大量的解释。–《深入理解C++11》
右值由两个概念构成,一个是将亡值,一个是纯右值,也有人说右值的别名就是将亡值,因为生动形象,这里我们采用后一种说法。
也有人把左右值定义为,左值可以读写,右值只能读不能写。
左值和右值的区分
  • 赋值表达式中,左边的全是左值,右边的不一定都是右值,但是右值一定在右边。听起来有点绕
int main() { int i = 1; int j = i; return 0; }

C++学习笔记|C++11常用特性
文章图片

比如a=b+c,&(b+c)编译时报错,即(b+c)的结果是一个临时变量不能取地址,所以b+c是右值,a是左值。
  • 将亡值,听名字就知道是一个即将死亡的值,换句话说,生命周期即将结束的值。比如一些临时变量的值,一些字面值,lambda表达式,都可以看做右值(将亡值)。
1,‘a‘ , true是字面值,也都是右值
一些临时变量,比如一些函数的返回值(返回值是左值引用的另说),1+3,“123”+“456”,lambda表达式,函数sum(1,2)的返回值,这些都是右值,将亡值的概念用在这就十分合适,用完就销毁了。
单个变量的引用算左值
左右值引用
  • 左值引用,接收左值的引用+const引用
比如T& 和const T&,const T&可以接受右值
const引用可以理解为接收一个临时的左值,const左值引用只能取地址不能赋值,const左值引用既能接收左值也能接收右值,算是一个例外。
move(左值)的作用是把这个左值转为一个右值,但是也可能带来一些问题,比如数据丢失。
  • 右值引用,T&&,两个&是为了区分是左值引用还是右值引用
int main() { int a = 1; //左值引用 int& b = a; const int& x = 1; //const引用是例外 int& c = 5; //err //右值引用 int&& m = 1; int&& n = a; //err int&& p = move(a); return 0; }

C++学习笔记|C++11常用特性
文章图片

左右值引用也符合精准匹配原则,哪个最符合参数类型就会选择哪个函数。
右值引用后被存储到一个特定的位置,如下所示
C++学习笔记|C++11常用特性
文章图片

左右值引用的作用 看到这,大概对左右值有了一个大概的认识。左值引用我们之前就知道,作用是可以减少拷贝,一定程度上替代了指针(指针有些地方是无法替代的),右值引用的作用是实现移动语义和完美转发。
移动语义:减少拷贝,提高效率
完美转发:保持原来的值属性不变。比如传参,左值传过去还是左值,右值传过去还是右值,利用这一点也可以提高程序的性能,比如减少拷贝。
简单来说,右值引用提高了程序的性能。(在某些场景下提升很大)
移动语义 假如现在需要一个一次性对象A来拷贝构造另一个生命周期更长的对象B,一次性对象A用完就销毁了,但是拷贝构造B前还是得去构造这个一次性对象A,是不是觉得有点浪费。
反正这个一次性对象A的作用也是拷贝出另一个对象B,反正一次性对象A用完就要销毁了,那我们有没有一种办法把A的资源“偷走”给拷贝出来的对象B,这样不就可以减少一次拷贝构造。
移动语义可以解决这个问题。
场景 看下面的场景
class String { public: String() = default; String(const char* string) { printf("构造\n"); _size = strlen(string); _string = new char[_size]; memcpy(_string, string, _size); } String(const String& other) { printf("深拷贝\n"); _size = other._size; _string = new char[_size]; memcpy(_string, other._string, _size); } ~String() { delete[] _string; } void Print() { for (size_t i = 0; i < _size; i++) { printf("%c", _string[i]); } printf("\n"); } private: char* _string; size_t _size; }; class Entity { public: Entity(const String& name)//可以看出构造出来的就只在这里用了 :_name(name)//拷贝构造 { printf("构造Entity\n"); } void PrintName() { _name.Print(); } private: String _name; }; int main() { Entity entity("ccl"); entity.PrintName(); return 0; }

场景中打印entity对象的名字就得需要先构造一个临时对象name,再拿临时对象name去拷贝构造出Entity类的成员_name,这就是两次深拷贝。
C++学习笔记|C++11常用特性
文章图片

运行结果也印证了深拷贝两次。
C++学习笔记|C++11常用特性
文章图片

那移动语义怎么解决这个问题呢?
或者说移动语义怎么做到偷走它的资源的呢。思路上就是把要用的指针指向本该销毁的内存,再把一次性对象的指针置空,之后一次性对象生命周期结束调用析构函数也不过是delete nullptr,这是合法的。
具体代码可以这样做:
String增加一个移动构造,Entity的构造也增加一个右值的版本由于匹配右值“ccl”
看看具体的更改:
C++学习笔记|C++11常用特性
文章图片

C++学习笔记|C++11常用特性
文章图片

代码汇总:
class String { public: String() = default; String(const char* string) { printf("构造\n"); _size = strlen(string); _string = new char[_size]; memcpy(_string, string, _size); } String(const String& other) { printf("深拷贝\n"); _size = other._size; _string = new char[_size]; memcpy(_string, other._string, _size); } String(String&& other) noexcept//提供一个移动构造的函数 参数是右值 { _string = other._string; _size = other._size; other._string = nullptr; //必须处理这个临时的字符串,不然两个指针指向同一块内存 other._size = 0; } ~String() { delete[] _string; } void Print() { for (size_t i = 0; i < _size; i++) { printf("%c", _string[i]); } printf("\n"); } private: char* _string; size_t _size; }; class Entity { public: Entity(const String& name)//可以看出构造出来的就只在这里用了 :_name(name)//拷贝构造 { printf("构造Entity\n"); } Entity(String&& name) :_name(std::move(name))//move可以把左值转换为右值,用于匹配String的移动构造 { printf("移动构造Entity\n"); } void PrintName() { _name.Print(); } private: String _name; }; int main() { Entity entity("ccl"); entity.PrintName(); return 0; }

再来看看结果
C++学习笔记|C++11常用特性
文章图片

比对一下增加移动拷贝前后的结果
C++学习笔记|C++11常用特性
文章图片

随笔记录:编译器优化,如果存在连续的构造等就会进行优化。
  • 为什么不直接进行优化?当没有对象接收时如果不产生临时变量会出问题,拿到的就是随机值
  • 利用将亡值的特点提高效率,接管即将销毁的空间,减少了一次深拷贝构造
  • 不优化的情况下,不管是左值还是右值都是两次构造,只是移动构造效率比左值效率要高不少
  • 一个深拷贝的类可以进一步实现移动拷贝,面对一些函数值返回的场景(返回的值是会借助临时变量,临时变量是右值),可以进一步的减少深拷贝,提升效率。
  • 很多旧容器都增加了一些右值引用相关的接口提升效率。比如vector
C++学习笔记|C++11常用特性
文章图片

最后,移动语义本质上允许我们移动对象,换句话说你可以吧一个已经存在的变量转成临时变量,你可以从这个特定的变量中窃取资源,也就达到了提高性能的目的。
move move的作用就是将左值转为右值。
上面通过拷贝构造窃取了资源,那假如我们想通过赋值来直接窃取资源呢?重载=即可。
注意点:处理旧数据,不能窃取自己的资源,防止一块内存被析构两次
String& operator=(String&& other) { if (this != &other)//窃取的不是自己的资源 { //处理旧数据,防止内存泄漏 delete[] _string; //窃取资源 _string = other._string; _size = other._size; //防止一块内存被析构两次 other._string = nullptr; other._size = 0; return *this; } }

调用时得用move转成右值取匹配赋值的右值版本。
C++学习笔记|C++11常用特性
文章图片

用move时得注意可能把数据转走了,毕竟是把左值转成了右值去使用,要是资源被窃取走了还去用就会造成一下问题。
完美转发 模板里的&&叫做万能引用,而不是右值引用。
void Test(int&) { cout << "左值引用" << endl; } void Test(const int&) { cout << "const 左值引用" << endl; } void Test(int&&) { cout << "右值引用" << endl; } void Test(const int&&) { cout << "const 右值引用" << endl; } template void PerfectForward(T&& t)//万能引用 { Test(t); //万能引用右值接受后变成左值 } int main() { int a = 1; const int b = 1; PerfectForward(a); PerfectForward(b); PerfectForward(1); return 0; }

C++学习笔记|C++11常用特性
文章图片

万能引用改变了值的左右值属性,该怎么保留之前的属性呢,此时就需要std::forward完美转发保留传参过程中对象的原生类型属性。
使用万能引用
template void PerfectForward(T&& t)//万能引用 { Test(std::forward(t)); }

运行结果,发现右值保留了其属性。
C++学习笔记|C++11常用特性
文章图片

C++11的类里面新增了两个默认成员函数,即移动构造函数和移动赋值运算符重载。
  • 移动构造函数:自己没有实现移动构造且没有实现析构、拷贝构造、拷贝赋值重载才会生成默认的移动构造。
默认的移动构造对于内置类型逐字节拷贝,自定义类型调用其移动构造,若没有实现移动构造就调用其拷贝构造。
  • 移动赋值重载函数:自己没有实现且没有实现析构、拷贝构造、拷贝赋值重载会生成默认的移动赋值。
默认的移动赋值对于内置类型逐字节拷贝,自定义类型调用其移动赋值,若没有实现移动构造就调用其拷贝赋值。
default、delete、final、override
成员函数加上=default表示强制生成默认的函数。
成员函数加上=delete表示删除这个函数,表现为只声明不实现。
final,被final修饰的类不能继承,修饰的变量成为常量且经初始化后不能改变,修饰的虚函数子类无法重写。
override:写在函数参数列表后面表示这个函数重写了父类的虚函数,编译器就会去检查是否重写了,如果没有重写就会报错。
可变参数模板
可变参数就是参数的个数和类型都是任意的。
最典型的可变参数的使用就是printf了,不在乎有几个参数,反正都能接收。
C++学习笔记|C++11常用特性
文章图片

除了这个外,还可以想想Linux里的命令,后面可以加好几个修饰符,参数的个数类型等都是不定的。
下面就是一个可变参数的函数模板。
template//...表示模板的个数是不固定的 void ShowArguList(Args...agrs) {}

我们把…成为参数包,我们无法直接获取参数包里的具体参数,需要一点特别的办法来展开参数包获取具体的参数,下面列举两种方法:递归式和逗号表达式展开
可以拿到每个参数的值,自然也可以拿到值的类型。
递归展开
template void ShowArguList(T value)//递归终止 { cout << value << endl; } template//...表示模板的个数是不固定的 void ShowArguList(T value,Args...args) { cout << value << " "; ShowArguList(args...); }int main()//调用 { ShowArguList(1); ShowArguList(2, 'b'); ShowArguList(3, 'c', 3.33); ShowArguList(4, 'd', "hello"); return 0; }

C++学习笔记|C++11常用特性
文章图片

逗号表达式展开
template void ShowArguList(T value) { cout << value <<" "; } template void ShowArguList(Args ... args) { int arr[] = { (ShowArguList(args),0)... }; cout << endl; } int main() { /*ShowArguList(1); */ ShowArguList(2, 'b'); ShowArguList(3, 'c', 3.33); ShowArguList(4, 'd', "hello"); return 0; }

C++学习笔记|C++11常用特性
文章图片

逗号表达式那一行可以理解为一种语法,理解不了就记
(ShowArguList(args),0)…是C++17里的折叠表达式(fold expression),有兴趣可以百度。
emplace_back和push_back C++11之后就有了一系列的emplace函数,以STL中list的emplace_back(尾插)为例。
C++学习笔记|C++11常用特性
文章图片

可以看到参数列表是可变参数和万能引用。直接给结论:emplace_back的效率确实比push_back高一点。
一般对于传过来的参数都是就地构造(不需要移动或者拷贝),而push_back需要先构造出来再拷贝,如果传的是右值且存在移动构造,那emplace_back比push_back高效一点(可以少进行一次移动构造,详情可以参考官方文档的实例std::list::emplace_back - cppreference.com)
总的来说,插入右值且存在移动拷贝的情况下其实两者效率中emplace_back小优,但是差不了太多,因为只节约了一次移动构造。我的VS2022对push_back还进行了优化,下面是vs2022对push_back的优化。。。
C++学习笔记|C++11常用特性
文章图片

下面是我碰到没解决的一个问题,编译器是VS2022,背地里做了太多优化,在此也希望大家不必陷入这种语法细节的泥沼!
C++学习笔记|C++11常用特性
文章图片

STL增加的容器
C++学习笔记|C++11常用特性
文章图片

可以看出加了四个容器。
  • array,这个容器可以放越界,因为重载了operator[],越界直接报错,这也算是一个优点了…
原生数组的越界的检查得看编译器,有时检查的到有时检查不到,有点玄学
  • forward_list,单链表,list也是链表,不过底层是双向循环链表。所以forward_list的好处就是每个元素少存储了一个指针,少四个字节,一百万个元素也就是四百万个字节,也就是4M左右。也算是优点吧。。。此外forward_list没有提供尾插尾删,单链表尾插尾删要找尾,效率不高,所以没有提供相应的接口。
  • unordered_map和unordered_set就比较有用了,传送门unordered_set与set的比较
lambda表达式
lambda表达式可以看做是一次性的匿名的仿函数(对我们匿名,编译器内部会给他一个名字)
关于lambda表达式的场景,大多能用函数指针的地方都能用lambda替代。此外lambda也是有类型的,可以用auto接收。
这里简单提一下可调用类型
  • 什么是可调用类型?
类型定义的对象可以像函数一样去调用
  • 可调用类型包含哪几种?
函数指针,仿函数,lambda,包装器。
下面是lambda表达式的实例,说明lambda是有类型的,但是类型的字符串是编译器内部给的,这也是为什么说对于我们来说是"匿名"的,对于编译器来说不是匿名的原因。
C++学习笔记|C++11常用特性
文章图片

没学lambda之前给一个数组排序:
C++学习笔记|C++11常用特性
文章图片

学lambda之后给一个数组排序:
C++学习笔记|C++11常用特性
文章图片

个人认为最大的好处是不用取名了,程序的可读性也有很大提升,别人一看就知道是升序还是降序
官方文档:Lambda expressions (since C++11) - cppreference.com
微软:C++ 中的 Lambda 表达式 | Microsoft Docs
官方文档已经讲得很详细了,这里给一下常用的用法。
微软给的图:C++学习笔记|C++11常用特性
文章图片

[capature-list](parameters)mutable->return-type {statement}
[捕捉列表](参数列表)->返回值类型{函数体}
lambda参数解释 捕捉列表 捕捉列表,捕捉列表能够捕捉上文中的变量,编译器根据[]来判断下面的代码是否是lambda表达式,且只能捕捉父作用域 。
捕捉列表使得lambda十分灵活,捕捉列表使得lambda可以拿到外部的变量。
  • []:什么都不捕捉,代表不能访问局部变量,只能访问全局变量。
C++学习笔记|C++11常用特性
文章图片

  • [=]:当前函数栈帧内的所有变量以按值传递的方式捕捉,不能捕捉全局变量,但是可以用。
按值捕捉:
C++学习笔记|C++11常用特性
文章图片

不能捕捉全局变量:
C++学习笔记|C++11常用特性
文章图片

  • [&]:当前函数栈帧内的所有变量以按值传递的方式捕捉,不能捕捉全局变量,但是可以用。
C++学习笔记|C++11常用特性
文章图片

  • [var]:以按值传递的方式捕捉变量var
  • [&var]:以引用的方式捕捉变量var
捕捉变量可以混着用:
C++学习笔记|C++11常用特性
文章图片

C++学习笔记|C++11常用特性
文章图片

简单来说,捕捉列表可以拿到上文数据,全局变量可以随便用(读写都行),捕捉主要针对的是局部变量,值传递的变量可读不能写(有const属性),引用捕捉的变量可读可写。
具体的多用用就会了。
参数列表 和函数的一样
[](int a, int b) {return a < b; }; //(int a, int b)就是参数列表

mutable 取消lambda函数的const属性,一般来说lambda都是一个const函数,mutable可以取消其常量属性,但是用了mutable参数列表就不能省略
auto f = [=, &c]()mutable {c = 4, cout << b << " " << c << endl; };

->返回值类型
auto f = [=, &c]()->int {c = 4, cout << b << " " << c << endl; return 0; }; //->int表示返回值为int

可以省略,省略后编译器自动推导,但是写了的话必须就必须和参数列表一起。
{函数体} 这个就不用多说了吧。
关于省略
int main() { [] {}; []() {}; []() ->int {return 0; }; []() mutable->int {return 0; }; return 0; }

C++学习笔记|C++11常用特性
文章图片

要是写lambda经常报错就给他写全了,看个人习惯,我的习惯是写全,这样可读性好一点。
lambda的实现还是仿函数,就像范围for的本质也不过是编译器,只不过编译器帮我们把很多事做了。
随笔记录:static在函数结束后还在
std::string析构时把size和capacity都置为0了
C++11之后STL库里的swap和algorithm里面的效率差不多了,因为有了右值引用。
const静态成员可以给缺省值(静态成员直接给缺省值是不行的)
C++学习笔记|C++11常用特性
文章图片

包装器
function - C++ Reference (cplusplus.com)
C++学习笔记|C++11常用特性
文章图片

function包装器,也叫适配器,本质上是类模板。
头文件是functional。
用法 function<返回值(参数列表)>包装器的名字=
C++学习笔记|C++11常用特性
文章图片

你可能在想,这不就是给函数起个名字吗,有什么大不了的。
但是!function是一个类啊,类即类型,自定义类型就可以和STL容器等等交互,那场景一下就大了很多。
function因为是一个类型,所以可以玩出很多花样,比如下面的
int main() { std::map>m { {"+",[](int a,int b)->int {return a + b; }}, {"-",[](int a,int b)->int {return a - b; }}, {"*",[](int a,int b)->int {return a * b; }}, {"/",[](int a,int b)->int {return a / b; }} }; cout << m["+"](1, 2) << endl; cout << m["-"](1, 2) << endl; cout << m["*"](1, 2) << endl; cout << m["/"](1, 2) << endl; return 0; }

C++学习笔记|C++11常用特性
文章图片

bind C++学习笔记|C++11常用特性
文章图片

本质上是一个函数模板,不过未作探究
直接看用法吧
绑定普通函数 C++学习笔记|C++11常用特性
文章图片

固定(绑定)参数的值
int Sum(double a, double b) { return a + b; } struct Sum2 { int operator()(double a, double b) { return a + b; } }; int main() { using namespace std::placeholders; auto func_sum2 = std::bind(Sum, 5, _2); cout << func_sum2(1, 2) << endl; return 0; }

C++学习笔记|C++11常用特性
文章图片

bind也可以调换参数的顺序,我暂时没发现使用场景…
bind也可以绑定函数模板、成员函数等等…
绑定成员函数 绑定非静态的成员函树需要传this指针,用法如下
class Plus { public: int Sum(int a, int b) { return a + b; } }; int main() { using namespace std::placeholders; Plus plus; auto Plus_Sum = std::bind(&Plus::Sum, &plus, _1, _2); cout << Plus_Sum(1, 2) << endl; return 0; }

C++学习笔记|C++11常用特性
文章图片

C++11线程库的之后会单写一篇记录。
总结
  • C++11增加了{}初始化的方式,某种程度上加强了类型安全
  • auto推导类型,decltype拿到类型。
  • nullptr不是指针,而是一个有类型的对象,范围for本质也不过是迭代器。
  • 左右值引用里的右值引用用于移动构造,提高效率。移动构造本质上是浅拷贝,一句话概括就是接管即将销毁的资源,也可以说”偷资源“
  • 给类生成默认的函数版本,删除某个成员函数(合理的有声明无定义)。
  • 可变参数模板拿到任意个参数,有两种方式进行解包。
  • STL增加了一些容器,比如unordered_map,unordered_set,某些场景下比map/set性能更优秀。
  • lambda可以看做是一个一次性的函数,用法很灵活。
  • 包装器可以包装函数,仿函数,lambda,给他们一个名字,由于包装器是一个类型,所以可以和一些容器用在一起实现一些花哨的操作,包装器可以与bind一起用,bind可以用来绑定数据。
【C++学习笔记|C++11常用特性】github代码汇总

    推荐阅读