[学习][记录] c++语言:从放弃到入门 <一> c++11新关键字以及引入的新特性

参考 c++11新特性,所有知识点都在这了!

文章目录


一、nullptr

nullptr 是用于解决 NULL 和 0 的有疑义关系的。NULL 通常被义为(void*)0。在 如下应用中会引发歧义。

1.1 入参

#include <iostream> 
using namespace std; 

void f(int){} 
void f(bool){} 
void f(void*){} 
int main() {
 f(0); // 调用f(int)
 f(NULL); // 可能无法编译通过,但一般会调用f(int)
 f(nullptr); // 调用f(void*)
}

1.1.1 解释

  1. C++ 视 0 首先为 int 型,因此,调用 f(0) 即调用 f(int)
  2. NULL 的情况复杂些,C++ 首先视其为广义整型。假如 NULL 被定义为普通 的 0,则调用 f(int); 如果 NULL 被定义成 0L,则 long -> int, long -> bool, 0L -> void*, 这三 种情况都是合法的,此时,编译器会报错
  3. 使用 nullptr,则不会有重载函数调用模糊的问题 - nullptr 不属于广义整型,也不是普通意义上的指针。 - nullptr 的实际类型是 std::nullptr_t,它能够隐式的转换成所有的原始指针 类型,故可将其视为一个可指向所有类型的指针。

1.1.2 办法

避免NULL重载编译报错:

f((int)NULL) 通过强转的办法解决

1.2 返值

使用 0 与 result 作比较,则一时不能确定 findRecord 的返回值类型 (可能是 广义整型,也可能是指针类型); 使用 nullptr,可以清楚地知道 findRecord 的返回值, 必定是一个指针类型。

auto result = findRecord( /* arguments */ );
 if (result == 0) { ... }
 
auto result = findRecord( /* arguments */ ); 
if (result == nullptr) { ... }

二、override

2.1 含义

覆盖重新写从父类继承过来的函数
覆写父类的虚函数时候,好的 IDE 一定会给出斜体等的提示,表示此函数覆写自 父类。

2.2 目的

避免发生,编译通过,但是逻辑错误的情况 例如函数名写错了 但是编译通过,却不是从父类继承过来的函数。
利用关键字 override 则指明,此种覆写关系,若此关系不成立,则以报错的形式提示 给用户。

2.3 问题?

class G{
public:
	virtual void func(int) {
		printf("G::func(int) \n");
	};

};

class H:G{
public:
	//virtual void func(int) override{
	//	printf("H::func(int) \n");
	//}

	virtual void func(double){
		printf("H::func(double) \n");
	}
};

void main() {
	H *p = new H; 
	p->func(5); 
	p->func(5.0); 

	system("pause");
}

vs2015
输出
H::func(double)
H::func(double)
请按任意键继续. . .

疑问???
没调用到G类的func(int)

三、final

3.1 含义

关键字 final 有两个用途:

第一,它阻止了子类继承;

class A final 
{
public: 
	virtual void func() const; 
};

class B: A //错误 编译报错 A is final
{
public: 
	void func() const override final;//  错误 编译报错 A is final
{
};

第二,阻止一个虚函数的覆 写。

class A 
{
public: 
	virtual void func() const; 
};

class B
{
public: 
	void func() const override final;//OK 
};

class C: B {
public:
	void func() const; //error, B::func is final  编译报错
};

3.2 意义

阻止类的无限扩展。

四、 =default =delete

4.1 default

C++ 的类有四类特殊成员函数,它们分别是:

  1. 默认构造函数
  2. 析构函数
  3. 拷贝构造函数
  4. 拷贝赋值运算符。
class A {
public:
 	A();//构造函数 创建类的实例时 调用
 	~A();//析构函数 销毁类的实例时 调用
 	A(const A &) =;//拷贝构造函数 类实例之间的拷贝 例如A a;A b = a;//此时调用拷贝构造函数
 	A operator=(const A &);//拷贝赋值运算符   本质重载运算符=进行初始化
}

关键字default
如果程序员没有显式地为一个类定义某个特殊成员函数,而又需要用到该特殊成员 函数时,则编译器会隐式的为这个类生成一个默认的特殊成员函数。

class A {
public: A() = default; 
A(int x ):_x(x) {} 
private: int _x; 
};

4.2 delete

为了能够让程序员显式的禁用某个函数,C++11 标准引入了一个新特性: "=delete"函数。程序员只需在函数声明后上“=delete;”,就可将该函数禁用。

class Singleton {
public:
 static Singleton* getInstance() {
  if(_ins == nullptr) 
  _ins = new Singleton; 
  return _ins; 
 }
 Singleton(Singleton &) = delete; 
 Singleton& operator=(Singleton &) = delete; 
private: 
 Singleton(){
 } 
 static Singleton* _ins; 
};

Singleton* Singleton::_ins = nullptr; //类的静态变量在类外初始化

int main() { 
 Singleton *ps = Singleton::getInstance(); 
 Singleton ps(*ps); 
 *ps = Singleton::getInstance(); 
 return 0; 
}

4.3 Raw String Literals

4.3.1 缘由

C/C++中提供了字符串,字符串的转义序列,给输出带来了很多不变,如果需要 原生义的时候,需要反转义,比较麻烦。

4.3.2 使用

C++提供了 R"()",原生字符串,即字符串中无转义,亦无需再反义。但是注意() 中的)"会导至提前结束。


#include <iostream> 

using namespace std;

string path = "C:\Program Files (x86)\alipay\aliedit\5.1.0.3754"; // 错误,\没转义
string path2= "C:\\Program Files (x86)\\alipay\\aliedit\\5.1.0.3754"; //正确,\转义了 用\\或者/,但麻烦
string path3= R"(C:\Program Files (x86)\alipay\aliedit\5.1.0.3754)"; //正确,使用了R()
string path4= R"(C:\Program "Files" (x86)\\alipay\aliedit\5.1.0.3754)";//正确,使用了R()

//string path5= R"(C:\Program "Files" (x86)\\alipay\aliedit\)"5.1.0.3754)";//错误,R()的缺陷

int main(int argc, char *argv[]) {
	cout << path.c_str() << endl;
	cout << path2.c_str() << endl;
	cout << path3.c_str() << endl;
	cout << path4.c_str() << endl;
	system("pause");
	return 0; 
}



输出结果:
C:Program Files (x86)lipayliedit.1.0.3754
C:\Program Files (x86)\alipay\aliedit\5.1.0.3754
C:\Program Files (x86)\alipay\aliedit\5.1.0.3754
C:\Program "Files" (x86)\\alipay\aliedit\5.1.0.3754


五、Range-Based for 循环Foreach

一种基于STL容器遍历的一种for循环形式 类似Java的 foreach

5.1 语法形式:

for (declaration : expression) 
      statement

例如
vector<int> vecInt = {0,1,2,3,4,5}; //expression 列表
for(auto &i:vecInt){//auto &i  is declaration 申明
	cout << i << endl; 
}

等价写法1
vector<int> vecInt = { 1,2,3,4,5 };
for (int i = 0; i<vecInt.size(); i++) {
	int tmp = vecInt.at(i);
	cout << tmp << endl;
}

等价写法2
vector<int> vecInt = { 1,2,3,4,5 };
vector<int>::iterator itr = vecInt.begin();
for (; itr != vecInt.end(); itr++) {
	cout << *itr << endl;
}

5.2 解释:

expression 部分是一个对象,必须是一个序列,比方说
用花括号括起来的初始值
1.列表
2.数组
int[] a={1,2,3,4,5}; char ch[] = “123”;string str = “china”;
3.vector 或 string 等类型的对象。
vector vet = {1,2,3};list list= {1,2,3} map<int,string>map = {1,“123”};
这些类型的共同特点是拥有能返回迭 代器的 begin 和 end 成员。

declaration 部分负责定义一个变量,该变量将被用于访问序列中的基础元素。
每 次迭代,declaration 部分的变量会被初始化为 expression 部分的下一个元素值。
确 保类型相容最简单的办法是使用 auto 类型说明符。

5.3 关于vector

1.vector::size 会遍历vector 效率会低

六、std::for_each()

#include "stdafx.h"
#include<algorithm>
#include<iostream>
#include<vector>

void func(int n)
{
    std::cout << n << std::endl;
}

int main()
{
    std::vector<int> arr;
    arr.push_back(1);
    arr.push_back(2);

    std::for_each(arr.begin(), arr.end(), func);

    return 0;
}

七、STL容器的使用

7.1 normal init 常规初始化

所谓的常规初始化,即调用类的构 造器。

7.1.1 vector

vector<int> vi; 
vector<int> vi(10,10);//size 10,each value 10
vector<int> vi(arr,arr+10);//begin,end

7.1.2 list

list<int> li(10);
list<int> li2(10);

list<int> li3(10,1);//size 10,each value 1

int arr2[10] = {};
list<int> li4(arr2,arr2+10);//begin,end

7.1.3 map

7.1.3.1 查找
7.1.3.2 插入

//map[key] = value
map<int,string> mis; 
mis[0] = "first"; 
mis[1] = "second"; 
mis[2] = "third"; 


//make_pair
map<int,string> mis2(mis.begin(),mis.end());
mis.insert(std::make_pair(3, "fourth"));
mis.insert(pair<int, string>(3, "fourth"));

//value_type
mis.insert(map<int, string>::value_type(3, "fourth")); 
for (auto& pair : mis) 
	cout << pair.first << ":" << pair.second.c_str() << endl;
7.1.3.3 删除

7.2 initialization List {} 列表方式初始化

以初始化列表的方式来进行实始化,打破了,原有的初始化格局,令实始化更直观 化,人性化。

vector<int> vi = {1,2,3,4,5}; 
list<int> li = {1,2,3,4,5}; 
map<int,string> mis = { 
	{1,"c"}, {2,"c++"}, 
	{3,"java"},{4,"scala"},
	{5,"python"}
	};
	
mis.insert({6,"ruby"}); 

for(auto &is: mis) {
 cout<<is.first<<is.second<<endl; 
}

7.3 容器的使用

7.3.1 std::map

插入
查找
删除

7.3.2 std::vector

插入
查找
删除

7.3.3 std::list

插入
查找
删除

八、initializer_list 类

8.1 原理

是使用 {} 而不是 () 调用构造函数,
template< class T > class initializer_list; C++11 中提供了新的模板类型 initializer_list。 initializer_list 对象只能用大括号{}初始化,其内有元素都是 const 的。常用于构造 器和普通函数参数。

8.2 常见用法:

8.2.1 普通函数参数

double sum(const initializer_list<double> &il) 
{
 double s = 0; 
 for(auto d:il)
  s += d; 
 return s; 
}

double s = sum({1,2,3,4,5});

8.2.2 构造器参数

template <typename T> 
class myarr {
private: 
 	vector<T> _arr;
public: 
	myarr() {
	 cout<<"called myarr()"<<endl;
	}
	myarr(const initializer_list<T>& il) {
	 cout<<"called myarr(const initializer_list<T>& il)" << endl; 
	 for (auto x : il)
	  _arr.push_back(x); 
	}
};

int main() {
 myarr<int> ma;
 myarr<int> ma2 = {1,2,3,4,5}; 
 return 0; 
}

8.3 格式

={},({}),{};
会调用initilizer_list构造

int arr[] = {1,2,3};
int arr2[] {1,2,3};
vector<int> vi = {1,2,3}; 
vector<int> vi2{1,2,3};

九、auto 自动类型推导

9.1 注意

1.auto 能够实现类型的自我推导,并不代表一个实际的类型声明。auto 只是一个 类型声明的占位符
2. auto 声明的变量,必须马上初始化,以让编译器推断出它的实际类型,并在编译时将 auto 占位符替换为真正的类型。
3. c++11 auto 不能用于函数参数 c++14可以

9.2 应用

十、declrtype

auto 类型,作为占位符的存在来修饰变量,必须初始化,编译器通过初始化来确 定 auto 所代表的类型,即必须定义变量。 如果,我仅希望得到类型,而不是具体的变量产生关系,该如何作到呢?

10.1 推导规则

decltype(expr);
所推导出来的类型,完全与 expr 类型一致。
同 auto 一样,在编译期间完成,并 不会真正计算表达式的值,即上面的推导数并不会导致函数打印的。
expr 不可对类型 推导。

10.2 应用

十一、仿函数

重载了 operator()的类的对象,在使用中,语法类型于函数。故称其为仿函数。

11.1 operator()

#include <iostream> 
#include <vector> 
#include <algorithm> 
using namespace std;
class Compare {
public: 
	int operator()(int x, int y) {
	 return x>y;
	} 
};

int main() {
 vector<int> vi = {1,3,5,7,9};
 sort(vi.begin(),vi.end(),Compare()); 
 for(auto &i:vi) 
 	cout<<i<<endl;
}

11.2 带状态的 operator()

相对于函数,仿函数,可以拥用初始状态,一般通过 class 定义私有成员,并在声 明对象的时候,进行初始化。

#include <iostream> 
#include <vector> 
#include <algorithm> 

using namespace std;

class Compare {
public: 
	Compare(bool f=true):flag(f){
	} 
	int operator()(int x, int y) {
	 if(flag) 
	 return x > y; 
	 else return x < y; 
	 } 
protected: 
	bool flag;
};

int main() {
 vector<int> vi = { 1, 3, 5, 7, 9}; 
 // sort(vi.begin(),vi.end(),Compare(false)); 
 sort(vi.back(),vi.end(), Compare(true)); 
 for(auto &i:vi) 
 	cout<<i<<endl;
}

十二、Lambda

12.1 匿名函数

简短函数,就地书写,调用,即 Lambda 存在的意义,常用于取代作回调用的简 短函数指针与仿函数。 就地书写,因只有函数体,即无函数名,也称匿名函数。

12.2 Gammar 格式

12.2.1 格式

[capturelist] (parameterlist) mutable ->return type { function body }
翻译一下

12.2.2 解释

1.capturelist 捕获列表。
捕获列表,总是出现在 lambda 函数的开始处。事实上[]是 lambda 的 引用符。换句话说,编译器根据引出符判断接下来的代码是否是 lamba 函数。

2.parameterlist 参数列表。
与普通函数的参数列表一致。如果不需要传递参数,可以连同()一起省略。

3.mutable 默认情况下,lambda 函数总是一个 const 函数, mutable 可以取消其常量性, 所谓的常量性是指不可修改 capturelist 中的东西。在使用该修饰符时,参数列表不可以 省略(即使参数为空)。

4.->return type 返回类型。 用于追踪返回类型形式声明函数的返回类型。出于方便,不需要返回值 的时候可以连同->一起省略。此外返回类型明确的情况下,也可以省略该部分。编译 器可以自行推导。

5.{ function body } 函数体。 内容与普通函数一样,不过除了可以使用参数之外,还可以使用所有捕获 的变量。

十三、 enum和enum class(struct)

13.1 C语言与c++:

 enum 对象,是可以被其它非枚举值,赋值,
 而 C++中的枚举变量, 则只能用枚举值来赋值。

13.2 C+±>C++11:

1.枚举体的声明和定义使用 enum class 或是 enum struct, 二者是等价的。使用 enum class\enum struct 不会与现存的 enum 关键词冲突。而且 enum class\enum struct 具有更好的类型安全和类似封装的特性(scoped nature)。

13.3 问题描述

1 向整形的隐式转换(Implicit conversion to an integer)
2 无法指定底层所使用的数据类型(Inability to specify underlying type)
3 enum 的作用域(Scope)
4 不同编译器解决该问题的方法不统一

13.4 作用域

13.4.1 类内的枚举

 class Painter {
 public: 
 enum Color { red, green, yellow};
 public:
  Painter() {
  }
  void dis(Color c) {
   cout<<c<<endl; 
  } 
};

13.4.2 带作用域的枚举

enum class Color { red, green, yellow}; 

13.5 指定类型

默认的底层数据类型是 int,
用户可以通过:type(冒号+类型)例如 class colorX:char
来指定任何整形 (除了 wchar_t)作为底层数据类型。

#include <iostream> 
using namespace std;
 
#include <iostream> 
enum class color:int { red, green, yellow}; 
enum class colorX:char { red, green, yellow }; 
int main() {
//使用域运算符访问枚举体成员,强转后打印 std::cout << static_cast<int>(color::red) << std::endl; std::cout << static_cast<int>(colorX::red) << std::endl; 
 return 0; 
}

十四、 assert/static_assert

14.1 assert 运行期断言,它用来发现运行期间的错误

不能提前到编译期发现错误, 也不具有强制性,也谈不上改善编译信息的可读性,既然是运行期检查,对性能当然是 有影响的,所以经常在发行版本中,assert 都会被关掉。

#include <iostream> 
#include <assert.h> 
using namespace std; 
char * myStrcpy(char *dest, const char *src) {
 assert(dest); assert(src); 
 while(*dest++ = *src++); 
 }
 int main(int argc, char *argv[]) {
 // char buf[1024]; char * p = NULL; // myStrcpy(buf,p); // cout<<buf<<endl; 
 FILE *fp = fopen("aa.c","w"); 
 assert(fp); return 0; 
 }

14.2 static_assert(提前判误)

static_assert 这个关键字,用来做编译期间的断言,因此叫做静态断言。其语法 很简单:static_assert(常量表达式,提示字符串)。
如果第一个参数常量表达式的值为真(true 或者非零值),那么 static_assert 不做 任何事情,就像它不存在一样,否则会产生一条编译错误,错误位置就是该 static_assert 语句所在行,错误提示就是第二个参数提示字符串。
使用 static_assert,我们可以在编译期间发现更多的错误,用编译器来强制保证 一些契约,并帮助我们改善编译信息的可读性,尤其是用于模板的时候。 static_assert 可以用在全局作用域中,命名空间中,类作用域中,函数作用域中, 几乎可以不受限制的使用。 编译器在遇到一个 static_assert 语句时,通常立刻将其第一个参数作为常量表达 式进行演算,但如果该常量表达式依赖于某些模板参数,则延迟到模板实例化时再进行 演算,这就让检查模板参数成为了可能。

十五、引用

15.1 右值引用定义 (r-value ref) &&

实质: 延长拷贝时产生的临时对象的生命周期

左值 指拥有稳定内存空间的内存区域 且拥有变量名
右值 指可能通过函数返回的临时变量 没有变量名 没有稳定内存空间 随时可能销毁的内存区域

左值引用 与 右值引用对比:

  1. 都属于引用类型。
  2. 都必须初始化。左值引用是具名变量值的别名,右值引用是匿名变量的别名。
  3. 左值引用,用于传参,好处在于,扩展对象的作用域。则右值引用的作用就在于 延长了临时对象的生命周期。
  4. 避免"先拷贝再废弃"带来的性能浪费。
  5. 对将亡值进行引用的类型;它存在的目的就是为了实现移动语义。

15.1.1 传引用和传指针,传右值引用

注意:1.栈对象的引用不可返回
栈对象返回,如果在没有优化的情况下-fno-elide-constructors,会产生临时对 象
在没有优化的情况下,要经历两次复制
2.拷贝不可避免

15.1.1.1 传引用
15.1.1.2 传指针
15.1.1.3 传右值引用

将函数返回的临时变量 延长 通过引用的方式 使用这个临时变量,减少拷贝次数

例如:

void func(string && str);

string getStr(){
		string strTmp;
 		return strTmp; //通过深拷贝strTmp的方式 返回一个tmp临时变量
}

void main(){
	  func(getStr());//tmp传入func 注意tmp 不是strTmp,只是一个拷贝的临时变量
}

如上 getStr()产生一个strTmp拷贝出来的临时变量tmp传入到func,func右值引用方式 延长tmp生命周期 使用tmp进行操作

15.2 const T & 万能常引用

func(const A &a)

特点:
const 可以实现常量引用,不同类型之引用(非基本数据类型也适用,但需要转化 构造函数)。 其本质也是产生了临时对象,并且该临时对象是 const 的。

缺陷:
可以在返回中避免临时对象,再次拷贝和销毁。但时临时对象的性质 是 const 的,也会给后续的使用带来不便。const 函数重载也是在这种反值 const 类型 的情型下诞生的。

func(A && a)

特点:减少拷贝次数,延长临时变量生命周期
缺陷: 对资源理解要深刻,慎用。

15.2.1 const 一些常见用法解释

std::ref

std::ref只是尝试模拟引用传递,并不能真正变成引用,在非模板情况下,std::ref根本没法实现引用传递,只有模板自动推导类型时,ref能用包装类型reference_wrapper来代替原本会被识别的值类型,而reference_wrapper能隐式转换为被引用的值的引用类型。

std::ref主要是考虑函数式编程(如std::bind)在使用时,是对参数直接拷贝,而不是引用

其中代表的例子是thread
比如thread的方法传递引用的时候,必须外层用ref来进行引用传递,否则就是浅拷贝。
C++11 std::ref和引用的区别

十六、 深赋值与浅赋值

【c++】深赋值与浅赋值

C++中的默认函数 深拷贝与浅拷贝 深赋值与浅赋值

十七、 深拷贝与浅拷贝

十八、 移动构造

对于类中,含有指针的情况,即含有堆内存资源的情况,要自实现其拷贝构造和拷 贝赋值。也就是所谓的深拷贝和深赋值。我想这己经成为一种共识了

实质:
将待返回对象的内容"偷"了过来。移 动构造的本质,也是一种浅拷贝,但此时的浅拷贝己经限定了其使用的场景,故而是安 全的。

18.1 慎用移动

拷贝,无疑是安全的,而移动,无疑是高效的。但是这种高效前提你对资源的深刻 的理解,否则可能带来极大的安全隐患的,所以要慎用。
移动,解决了,第一次拷贝的效率问题,
引用,解决了,第二次拷贝的效率 问题。

十九、 std::move

19.1 实质

move的名字本身比较迷惑,其本身并没有任何"移动",
它唯一的功能,就是将左 值转化为右值,完成移动语义,继而我们可以使用右值。

左值: 具有稳定存储空间,有变量名,作用域中有效 例如 int a;
右值: 具有不稳定存储空间,且无变量名,作为函数返回值的临时变量,用作拷贝到右值的中转存储,中转后即会销毁。
例如

  int func(){
  	  int b = 0;
  	  return b;
  }
 void main(){
 	  int a  = func();//func b拷贝一份到临时变量 tmp 再拷贝给a 
 }

如果说,产生临时对象,隐式的调用了移动构造,
std::move是一种显示调用移动 构造的方法。即刻意营造一种可以使用移动的情景。

19.2 应用

可以 move 的类中,其父类成员,和成员对象的也要支持 move,就要借助 std::move。

#include <iostream>
using namespace std;
class Complex {
public:
    Complex(int f = 0) :_f(new float(f)) {
        cout<<"Complex()"<<endl;
    }
    Complex(const Complex & another) :_f(new float(*another._f)) {
        cout<<"Complex(const Complex & another)"<<endl;
    }
    Complex(Complex && another) :_f(another._f) {
        another._f = nullptr;
        cout<<" Complex(Complex && another)"<<endl;
    }
    Complex& operator = (const Complex & another) {
        cout<<"Complex& operator = (const Complex & another)"<<endl;
        if(this != &another) {
            delete _f; _f = new float; *_f = *another._f;
        }
        return *this;
    }
    Complex& operator = ( Complex && another) {
        cout<<" Complex& operator = (const Complex && another)"<<endl;
        if(this != &another) {
            delete _f; _f = another._f;
        }
        return *this;
    }
    ~Complex() {
        if(_f != nullptr)
            delete _f;
    }
    float *_f;
};
class Moveable {
public:
    Moveable(int i) :_i(new int(100)),_c(2.1) //先父类,再类对象,再本类
    {
        cout<<" Moveable(int i)"<<endl;
    }

    Moveable(const Moveable &another ) :_i(new int(*another._i)) ,_c(another._c) {

        cout<<" Moveable(const Moveable &another )"<<endl;
    }

    Moveable(Moveable &&another) {
        cout<<" Moveable(Moveable &&another)"<<endl;
        _i = another._i;
        another._i = nullptr;
        _c = std::move(another._c); //走 Complex 移动赋值
    }
    ~Moveable()
    {

            if(_i != nullptr) delete _i;
    }

    int *_i;
    Complex _c; //类对象成员
};
int main(int argc, char *argv[]) {
    Moveable m(100);
    Moveable n(std::move(m)); //走移动构造
    return 0;
}

19.3 移动构造在STL中的应用

二十、 std::function

include <functional>

20.1 含义

回调函数(包括,普通函数,函数指针, lambda,仿函数)的统一包装,相当于函数的抽象类或者接口,可以用统一的方式保存。

用法:
std:function<retType(argType,argType,…)>
retType 函数返回值
argType 参数类型

20.2 应用

20.2.1 function 作参数类型实现回调

void selectSort(int *p, int n, function<bool(int, int)> pCompare)selectSort(arr,10,
         [](int x ,int y)
{
return x>y;
});

20.2.1.2 function 作类成员实现回调

class A 
{
public: 
A(const function<void()> & cb) 
:_callback(cb) 
{}
void notify() 
{ 
_callback(); 
}
function<void()> _callback; 
};


A a(fct); 
a.notify();

二十一、 std::bind

21.1 含义

bind 用来将可调用对象和参数一起进行绑定。可调用对象包括普通函数、全局函
数、静态函数、类静态函数甚至是类成员函数,参数包括普通参数和类成员。
语义
std::bind(funcName,argType,…);//绑定以存在的函数名,以及传入的实参
placeholders::_x 实参占位
placeholders::_1 表示第一个实参暂时不填实参数值,依次可推。

21.2 作用

21.2.1 绑定普通函数与参数及占位

double myDivide (double x, double y)auto fn_five = std::bind (myDivide, 10, 2); 
cout << fn_five() << endl;

21.2.2 绑定对象与成员及占位

class MyPair2 
{
public: 
  int add(int x, int y) 
  { 
return x + y; 
  } 
};

auto bindObjfunc = bind(&MyPair2::add,mp2 , 2, 3); 

21.2.3 函数重载情形

int add(int x, int y);
double add(double x, double y);


auto bindGlobalFunc = 
bind((int(*)(int,int))add,_1,_2);

auto bindGlobalFunc2 = 
bind(static_cast<double(*)(double,double)>(add),_1,_2);

注意
1.预先绑定的参数值通过 值传递进去;通过placehodler传递进去的 传递引用进去
2.bind 返回的是可调用的函数
3.绑定的参数 调用之前 需要确认是可用的

21.3 多态之 bind +fucntion

21.3.1.可实现多态使用

std::function<void(void)> f;
void foo()void func(int a)

f = std::bind(foo); 
f(); 
f= std::bind(func,1); 
f(); 

21.3.2.function 本是不可以包装类成员函数,bind实现类成员函数的绑定, 然后赋给 fucntion 对象,亦即实现了间接性的包装

std::function<void(void)> f;
class Foo 
{
public: 
void method() 
{ 
cout<<"Foo::void method()"<<endl; 
}
void method2(string s) 
{ 
cout<<"Foo:void method2()"<<endl; 
} 
};


Foo foo; 
f = std::bind(&Foo::method,&foo); 
f(); 
f = std::bind(&Foo::method2,&foo,"china"); 
f();

二十二、 Unordered Contrainer 无序容器

22.1 unordered_map

采用hash_map 数据结构,无序

二十三、 Auto Memeory Manage 自动内存管理

23.1 RAII(Resource Acquisition Is Initialization)

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制
程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。 RAII 的一般做法是这样的:在对象构造时获取资源,接着控制对资源的访问使之
在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际 上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
①不需要显式地释放资源。 ②采用这种方式,对象所需的资源在其生命期内始终保持有效。
此时,所托管的资源,随对象的创建而获取,随对象的消失而消失,即所谓的 RAII 思想:资源获取即初始化。

原理上,是代理了被托管的对象指针,管理对象的生命周期,即实现自动释放。其 行为类似于所托管的对象指针,原因是,重载了 operator->和 operator*。 如下是智能指针的模板(template)化实现。

template <class T> class SmartPtr {
public:
 	explicit SmartPtr(T* pointee) : pointee_(pointee){}
 	 ~SmartPtr() {
 	  delete pointee_;
 	 }
 	 
	T& operator*() const { //... 
	return *pointee_;
	}
	
	T* operator->() const { //... 
	return pointee_; 
	} 
	
private:
	 T* pointee_; //... 
};

23.2 auto_ptr(deprecated)

代理new出来的对象,自动化释放对象,作用域处结束,释放对象。

存在问题

  • 两个auto_ptr对象拥有同一个对象时,析构时都试图删除p,会出现崩溃问题
 #include <iostream> 
 #include <memory> 
 using namespace std; 
 int main() {
  int *p = new int(10); 
  {
   auto_ptr<int> ap(p);
   cout<<*ap<<endl;
  }
  auto_ptr<int> ap2(p);
  cout<<*ap2<<endl;
 return 0; 
}
  • 作参数传递的时,亦是会出现同样的情况,两个指针,对同一段资源产生了引用行为。

23.3 unique_ptr

uniqu_ptr 的使用方法,基本上等同于 auto_ptr, 不同之处,就在于实现了资源的 唯一, 既不可以拷贝也不可以赋值,正如其名字一样。

  • 解决了作为 参数拷贝时,无法拷贝和赋值的特性使 只能使用同一个对象,解决auto_ptr造成的释放崩溃
  • 判断是否有资源
unique_ptr<Copy> up; 
if(!up)
{
	cout<<"无资源托管"<<endl; 
}
unique_ptr<Copy> up2(new Copy(10)); 
if(up2)
{
 cout<<"有资源托管"<<endl;
 }
  • 允许托管左值 unique_ptr up2 =std::move(up); 相当于资源转移,被转移的资源不允许再使用
int main() { 
	unique_ptr<Copy> up(new Copy(99));
	cout<<up.get()<<endl;
	unique_ptr<Copy> up2 =std::move(up);
	cout<<up2.get()<<endl;
	//up.get();//被转移资源 再使用 会造成崩溃 
}

23.3.1 常用函数

  • get() 返回所托管资源的指针
  • release() 取消代理(放弃托管) ,返回资源句柄,注意:对象释放仍需手动维护
  • reset() 重置 1.参数为空 释放之前的资源对象 2.参数为对象指针,先释放之前的资源对象,重新托管新的对象
  • std::move() 允许托管左值 相当于资源转移,被转移的资源不允许再使用

23.4 shared_ptr

shared_ptr之间共享资源,共享同一个计数器,每次引用,引用计数+1

int main() { 
	shared_ptr<int> sp(new int(10)); 
	cout<<sp.get()<<endl;
	if(sp) {
	cout<<"有资源托管中"<<endl;
	} 
	cout<<sp.use_count()<<endl;
	 {
		shared_ptr<int> sp2 = sp; 
		cout<<sp2.use_count()<<endl; 
	}
	cout<<sp.use_count()<<endl;
	return 0; 
}

输出结果:
有资源托管中
1
2
1

比较:

  • uinque_ptr 解决了 auto_ptr 中引用同一个对象的问题,方式就是不可引用同一个 对象。
  • shared_ptr 解决了 auto_ptr 中引用同一个对象,共同拥有一个资源, 但不会重 析构的问题,原理是,在内部保持一个引用计数,并且仅当引用计数为 0 才能被删除, 不可以用数组。

都无法避免作用域重复析构崩溃

23.5 用法

  • 基本类型计数测试
#include <iostream>
 #include <memory> 
 using namespace std; 
 void func(shared_ptr<int> sp) {
 // sp.reset(); //此处仅将自己所累积的计数减 1 
 cout<<sp.use_count()<<endl; sp.reset(); //此时 reset 等价于 sp 对象消失,若己为零,则不再减 1. 
 }
int main() { 
shared_ptr<int> sp(new int(10)); 
cout<<sp.get()<<endl;
if(sp){
  cout<<"有资源托管中"<<endl;
}
cout<<sp.use_count()<<endl; 
shared_ptr<int> sp2 = sp; 
cout<<sp2.use_count()<<endl; 
cout<<sp.use_count()<<endl; 
func(sp);
cout<<sp.use_count()<<endl; 
return 0; 
}
  • 对象计数测试

reset 跟参数,会托管新对象,释放旧对象,如若不跟参数话,会将当前对象的引 用计数减 1

#include <iostream> 
#include <memory> 
using namespace std; 
class A {
public: A() { 
	cout<<"A()"<<this<<endl; }
~A() { 
	cout<<"~A()"<<this<<endl; 
}
void dis() { 
	cout<<"A::void dis()"<<endl; 
} 
};

int main1() { 
{ 
 shared_ptr<A> sp(new A);
 sp.reset(new A()); 
 cout<<"+++++++++++++++++++++"<<endl; 
}
 
cout<<"======================"<<endl;
}

int main() 
{ 
	{ 
		shared_ptr<A> sp(new A); 
		sp.reset();sp.reset();sp.reset(); 
		cout<<"+++++++++++++++++++++"<<endl; 
	}
	cout<<"======================"<<endl; 
}
  • 对象传参测试
#include <iostream> 
#include <memory> 

using namespace std; 

class A {public: A() {
 cout<<"A()"<<this<<endl; 
}

~A() { 
cout<<"~A()"<<this<<endl; 
}

void dis() { 
 cout<<"A::void dis()"<<endl; 
} 
};

void func(shared_ptr<A> &sp) //shared_ptr<A> &sp 
{ 
	cout<<sp.use_count()<<endl; 
	sp.reset(); sp.reset(); 
	cout<<"======================"<<endl; 
}

int main() 
{ 
	shared_ptr<A> sp(new A);
	cout<<sp.use_count()<<endl; 
	func(sp); 
	cout<<sp.use_count()<<endl; 
	shared_ptr<A> sp2 = std::move(sp); //移动会将计数也一起移走 
	cout<<sp2.use_count()<<endl; 
	cout<<sp.use_count()<<endl; //此时资源为 0 
	return 0; 
}

对同一个对象,多次 reset 的结果,是仅对自己增加的计数减 1。要保证,当前的 对象的使用安全性。也不会对其它象的使用造成影响。

void func(shared_ptr<Copy> spc) //&spc 若传递的是引用,则引用计数不会加 1。离开函数也不会减 1 
shared_ptr<A> sp2 = std::move(sp); //移动会将计数也一起移走

23.5.1 常用函数

  • reset() 只可自杀 不可他杀
  • std::move() 计数不会增加
  • operator bool 判空
  • operator= 支持赋值,复制

23.6 weak_ptr

#include <memory>
std::weak_ptr<int> wp1;

只能和 shared_ptr 类型指针搭配使用。甚至于,我们可以将 weak_ptr 类型指针视为 shared_ptr 指针的一种辅助工具。
借助 weak_ptr 类型指针, 我们可以获取 shared_ptr 指针的一些状态信息:

  • 有多少指向相同的 shared_ptr 指针、
  • shared_ptr 指针指向的堆内存是否已经被释放等等。

注意:
当 weak_ptr 类型指针的指向和某一 shared_ptr 指针相同时,weak_ptr 指针并不会使所指堆内存的引用计数加 1;
同样,当 weak_ptr 指针被释放时,之前所指堆内存的引用计数也不会因此而减 1。
也就是说,weak_ptr 类型指针并不会影响所指堆内存空间的引用计数。

二十四、 Thread框架

24.1 join 与 detach

t.join 和 t.detach 标志着,线程对象和线程的关系。t.join 表识,线程与线程对象 的同步关系。而 t.detach 表识,线程与线程对象的异步关系。
join 是阻塞的。

注意:主线程结束后 detach() 可能会还没运行就销毁了

24.2 传参方式

线程,有自己独立的栈。可以共享全局的变量。在线程启动的时候可以传入启动参数。

1.传值
std::thread threadTest(func,arg1,arg2,…);
2.传引用
std::thread threadTest(func,std::ref(arg1),std::ref(arg2));

24.3 常用函数

join()  //阻塞运行线程
joinable() // 线程是否阻塞的
detach() // 异步运行
sdt::ref() //引用化

24.4 同步之mutex

24.5 volatile

修饰变量,此变量可能被多线程访问和修改,加上此关键字,可以避免编译器优化,不使用存储在寄存器中的值,而是每次都去内存里去读。

24.6 lock(),unlock()

对某作用域加锁,解锁,但如果作用域抛异常可能会导致解锁失败,产生死锁。
可以控制加锁粒度。

24.7 try_lock(),unlock()

尝试加锁,加锁失败会返回false,

try_lock()
1.如果互斥锁当前未被任何线程锁定,则调用线程将其锁定(从此点开始,直到调用其成员解锁,该线程拥有互斥锁)。
2.如果互斥锁当前被另一个线程锁定,则该函数将失败并返回false,而不会阻塞(调用线程继续执行)。
3.如果互斥锁当前被调用此函数的同一线程锁定,则会产生死锁(具有未定义的行为)。 请参阅recursive_mutex以获取允许来自同一线程的多个锁的互斥锁类型。

24.8 std::lock_guard()

自动锁,声明范围内进行自动加锁解锁操作

在 lock_guard 对象构造时,传入的 Mutex 对象(即它所管理的 Mutex 对象) 会被当前线程锁住。在 lock_guard
对象被析构时,它所管理的 Mutex 对象会自动解 锁,由于不需要程序员手动调用 lock 和 unlock 对 Mutex
进行上锁和解锁操作, 因此这也是最简单安全的上锁和解锁方式,尤其是在程序抛出异常后先前已被上锁的 Mutex
对象可以正确进行解锁操作,极大地简化了程序员编写与 Mutex 相关的异常 处理代码。

#include <iostream> 
#include <thread> 
#include <mutex> 
using namespace std; 
mutex mtx; 
void printEven(int i) { 
if( i%2 == 0) cout<< i <<" is even"<<endl; 
else
   throw logic_error("not even"); 
}

void printThreadId(int id) { 
	try{
	lock_guard<mutex> lck(mtx); //栈自旋 抛出异常时栈对象自我析构。 			   
	printEven(id);
//		mtx.lock(); 
		//printEven(id); 
		//mtx.unlock(); 
	}catch(logic_error & ){ 
		cout<<"exception caught"<<endl; 
	} 
}

int main() { 
	thread ths[10]; //spawn 10 threads 
	for(int i=0; i<10; i++) { 
		ths[i] = thread(printThreadId,i+1); 
	}
	for(auto & th: ths) 
		th.join(); 
	return 0; 
}

24.9 死锁

死锁的原因是,container 试图多次去获取锁己获得的锁。
std::recursive_mutex 允 许多次获取相同的 mutex。
C++中 STL 中的容器,是非线程安全的。

24.10 std::recursive_mutex()

递归锁

  1. 调用线程从成功调用 lock 或 try_lock 开始占有recursive_mutex, 期间线程可以进行对 lock 或 try_lock的附加调用,所有权在线程调用 unlock 匹配次数时结束。

  2. 线程占有recursive_mutex时,若其他线程要求recursive_mutex所有权,调用lock将被阻塞,调用try_lock将返回false.

  3. 可锁定recursive_mutex的最大次数未指定的,但到达该数后,对 lock 的调用将抛出 std::system_error 而对 try_lock 的调用返回false;

  4. 若recursive_mutex在仍被线程占有时被销毁,则程序行为未定义。recursive_mutex满足 mutex 和 标准布局类型的所有要求。

24.11 同步之std::condition_variable

条件变量,多线程中对变量操作时对变量进行条件判断 从而当前线程需要是否阻塞或者被唤醒

条件变量(condition variable)是利用线程间共享的全局变量进行同步的一种机制,
主要包括两个动作:
一个线程等待某个条件为真,而将自己挂起;
另一个线程使的条件成立,并通知等待的线程继续。
为了防止竞争,条件变量的使用总是和一个互斥锁 结合在一起
C++11 中引入了条件变量,其相关内容均在<condition_variable>中。
这里主要 介绍 std::condition_variable 类。 条件变量 std::condition_variable 用于多线程之间的通信,它可以阻塞一个或同时阻塞多个线程。std::condition_variable 需要与 std::unique_lock 配合使用。 std::condition_variable 效果上相当于包装了 pthread 库中的 pthread_cond_*()系列 的函数。
当 std::condition_variable 对 象 的 某 个 wait 函 数 被 调 用 的 时 候 , 它 使 用 std::unique_lock(通过 std::mutex)来锁住当前线程。当前线程会一直被阻塞,直到另 外一个线程在相同的 std::condition_variable 对象上调用了 notification 函数来唤醒 当前线程。

24.11.1 成员函数

(1)、构造函数: 仅支持默认构造函数,拷贝、赋值和移动(move)均是被禁用的。
(2)、wait: 当前线程调用 wait()后将被阻塞,直到另外某个线程调用 notify_*唤 醒当前线程;当线程被阻塞时,该函数会自动调用 std::mutex 的 unlock()释放锁,使
得其它被阻塞在锁竞争上的线程得以继续执行。一旦当前线程获得通知(notify,通常 是另外某个线程调用
notify_*唤醒了当前线程),wait()函数也是自动调用 std::mutex 的 lock()。wait
分为无条件被阻塞和带条件的被阻塞两种。 无条件被阻塞:调用该函数前,当前线程应该已经对 unique_lock lck
完成了加锁。所有使用同一个条件变量的线程必须在 wait 函数中使用同一个 unique_lock。该 wait
函数内部会自动调用 lck.unlock()对互斥锁解锁, 使得其他被阻塞在互斥锁上的线程恢复执行。使用本函数被阻塞的当前线程在获得通知
(notified,通过别的线程调用 notify_*系列的函数)而被唤醒后,wait()函数恢复执行 并自动调用
lck.lock()对互斥锁加锁。
带条件的被阻塞: wait 函数设置了谓词(Predicate),只有当 pred 条件为 false 时 调用该 wait 函数才会阻塞当前线程,并且在收到其它线程的通知后只有当 pred 为 true 时才会被解除阻塞。因此,等效于 while
(!pred()) wait(lck)
(3)、wait_for: 与 wait()类似,只是 wait_for 可以指定一个时间段,在当前线程 收到通知或者指定的时间超时之前,该线程都会处于阻塞状态。而一旦超时或者收到了 其它线程的通知,wait_for 返回,剩下的步骤和 wait
类似。
(4)、wait_until: 与 wait_for 类似,只是 wait_until 可以指定一个时间点,在当 前线程收到通知或者指定的时间点超时之前,该线程都会处于阻塞状态。而一旦超时或 者收到了其它线程的通知,wait_until
返回,剩下的处理步骤和 wait 类似。
(5)、notify_all: 唤醒所有的 wait 线程,如果当前没有等待线程,则该函数什么也 不做。
(6)、notify_one: 唤醒某个 wait 线程,如果当前没有等待线程,则该函数什么也 不做;如果同时存在多个等待线程,则唤醒某个线程是不确定的(unspecified)。

24.12 std::unique_lock()

独占锁,当前锁作用域结束之前,其余线程无法使用mutex。

std::unique_lock对象以独占所有权的方式(uniqueowership)管理mutex对象的上锁和解锁操作,即在unique_lock对象的声明周期内,它所管理的锁对象会一直保持上锁状态;而unique_lock的生命周期结束之后,它所管理的锁对象会被解锁。
unique_lock具有lock_guard的所有功能,而且更为灵活。虽然二者的对象都不能复制,但是unique_lock可以移动(movable),因此用unique_lock管理互斥对象,可以作为函数的返回值,也可以放到STL的容器中。

24.13 std::atomic

c++11提供了原子类型std::atomic,理论上这个T可以是任意类型。
整形有这种原子变量已经足够方便,就不需要使用std::mutex来保护该变量啦。


struct OriginCounter { // 普通的计数器
	int count = 0;
	std::mutex mutex_;
	void add() {
		std::lock_guard<std::mutex> lock(mutex_);
		++count;
	}

	void sub() {
		std::lock_guard<std::mutex> lock(mutex_);
		--count;
	}

	int get() {
		std::lock_guard<std::mutex> lock(mutex_);
		return count;
	}
};

struct NewCounter { // 使用原子变量的计数器
	std::atomic<int> count = 0;
	void add() {
		++count;
		// count.store(++count);这种方式也可以
	}

	void sub() {
		--count;
		// count.store(--count);
	}

	int get() {
		return count.load();
	}
};

void main()
{
	NewCounter counter;
	std::thread thread1([&counter]() {
		int nTime = 10;
		while (nTime--)
		{
			counter.add();
			printf("thread1:%d\n", counter.get());
		}
		
	});

	thread1.detach();

	std::thread thread2([&counter]() {
		int nTime = 10;
		while (nTime--)
		{
			counter.add();
			printf("thread2:%d\n", counter.get());
		}

	});

	thread2.join();
	return;
}
thread1:1
thread1:2
thread1:4
thread1:5
thread1:6
thread1:7
thread1:8
thread1:9
thread1:10
thread1:11
thread2:3
thread2:12
thread2:13
thread2:14
thread2:15
thread2:16
thread2:17
thread2:18
thread2:19
thread2:20

24.14 std::call_once

c++11提供了std::call_once来保证某一函数在多线程环境中只调用一次,它需要配合std::once_flag使用。

类 std::once_flag 是 std::call_once 的辅助类。
传递给多个 std::call_once 调用的 std::once_flag 对象允许那些调用彼此协调,从而只令调用之一实际运行完成。
std::once_flag 既不可复制亦不可移动。
总之避免,某函数被多个对象调用,多个对象调用std::call_once时传入std::once_flag来判断该对象是否能调用目标函数。

#include <thread>
#include <mutex>
#include <iostream>

std::once_flag onceflag;

void CallOnce() {
	std::call_once(onceflag, []() {
		std::cout << "call once" << std::endl;
	});
}

int main() {
	std::thread threads[5];
	for (int i = 0; i < 5; ++i) {
		threads[i] = std::thread(CallOnce);
	}
	for (auto& th : threads) {
		th.join();
	}
	return 0;
}
call once

24.15 volatile相关

volatile修饰过的变量,编译器对访问该变量的代码通常不再进行优化。

24.16 异步相关

std::future

std::future用于访问异步操作的结果,从翻译上来讲就是将来的值,future.get()时会阻塞,如果有值,就立刻返回值;如果没有,则阻塞当前线程当前位置,直到有值,也就是所谓的异步;

std::promise

std::promise内部有个future 类似

class  Promise{
public:
  Future* m_pFuture;
}

std::promise 主要用来传递future包含的信息的 包括信息的set_value()、get_future()
当需要获取线程中的某个值,可以使用std::promise

std::packaged_task

当需要获取线程函数返回值,可以使用std::packaged_task。

std::packaged_task: (1).禁用拷贝赋值。(2).支持移动赋值。

std::promise与std::future配合使用

#include <functional>
#include <future>
#include <iostream>
#include <thread>
#include <windows.h>

int main() {
	std::promise<int> prom;
	std::future<int> fut = prom.get_future();
	std::thread thread1([&fut]() {
		int startCount = GetTickCount();
		printf("thread1 start :%d\n", startCount);
		int x = fut.get();
		std::cout << "value: " << x << std::endl;
		int endCount = GetTickCount();
		printf("thread1 end :%d\n", endCount);
	});
	thread1.detach();


	std::thread thread2([&prom]() {
		int value = 144;
		printf("thread2 set_value :%d\n", value);
		prom.set_value(value);
	});
	thread2.join();
	system("pause");
	return 0;
}
thread1 start :32191656
thread2 set_value :144
value: 144
thread1 end :32191656

留意输出结果会发现 有一个输出顺序,thread1 先等待一会,等待thread2set_value之后thread1才继续输出。
就是get时没有获得到值所以阻塞中的过程。

std::packaged_task与std::future配合使用

#include <functional>
#include <future>
#include <iostream>
#include <thread>

using namespace std;

int main() {
	std::packaged_task<int(int)> task([](int in)->int {
		return in + 1;
	});

	std::future<int> fut = task.get_future();
	std::thread(std::move(task), 5).detach();//move是因为std::packaged_task不支持拷贝构造,支持移动构造。
	cout << "result " << fut.get() << endl;
	return 0;
}
result 6

std::future用于访问异步操作的结果,
而std::promise和std::packaged_task在future高一层,它们内部都有一个future,promise包装的是一个值,packaged_task包装的是一个函数,当需要获取线程中的某个值,可以使用std::promise,当需要获取线程函数返回值,可以使用std::packaged_task。

24.17 std::async

async是比future,packaged_task,promise更高级的东西,
它是基于任务的异步操作,通过async可以直接创建异步的任务,返回的结果会保存在future中,
不需要像packaged_task和promise那么麻烦,关于线程操作应该优先使用async。

#include <functional>
#include <future>
#include <iostream>
#include <thread>
#include <Windows.h>

using namespace std;

int main() {
	auto res = std::async(std::launch::async,[](int in)->int {
		int start = GetTickCount();
		printf("func start %d\n", start);
		Sleep(2000);
		int end = GetTickCount();
		int n = in + 1;
		printf("func value %d\n", n);
		printf("func end %d\n", end);
		return n;
	}, 5);
	// res.wait();
	int beforeTime1 = GetTickCount();
	printf("Main Thread beforeTime %d\n", beforeTime1);
	cout << "Get Value:"<<res.get() << endl; // 阻塞直到函数返回
	int afterTime1 = GetTickCount();
	printf("Main Thread afterTime1 %d\n", afterTime1);
	return 0;
}
Main Thread beforeTime 34517000
func start 34517000
func value 6
func end 34519015
Get Value:6
Main Thread afterTime1 34519015

语法

std::future<T> std::async(std::launch::async | std::launch::deferred, func, args...);

std::future< T >:返回参数
参数1 执行策略:

  • std::launch::async表示任务执行在另一线程
  • std::launch::deferred表示延迟执行任务,调用get或者wait时才会执行,不会创建线程,惰性执行在当前线程。

如果不明确指定创建策略,以上两个都不是async的默认策略,而是未定义,它是一个基于任务的程序设计,内部有一个调度器(线程池),会根据实际情况决定采用哪种策略。

参数2 func:即将执行的函数
参数args…: 函数实参

24.3 常用关键字

noexcept

总结

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

二进制怪兽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值