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

文章目录

  • 一、nullptr
    • 1.1 入参
      • 1.1.1 解释
      • 1.1.2 办法
    • 1.2 返值
  • 二、override
    • 2.1 含义
    • 2.2 目的
    • 2.3 问题?
  • 三、final
    • 3.1 含义
    • 3.2 意义
  • 四、 =default =delete
    • 4.1 default
    • 4.2 delete
    • 4.3 Raw String Literals
      • 4.3.1 缘由
      • 4.3.2 使用
  • 五、Range-Based for 循环Foreach
    • 5.1 语法形式:
    • 5.2 解释:
    • 5.3 关于vector
  • 六、std::for_each()
  • 七、STL容器的使用
    • 7.1 normal init 常规初始化
      • 7.1.1 vector
      • 7.1.2 list
      • 7.1.3 map
        • 7.1.3.1 查找
        • 7.1.3.2 插入
        • 7.1.3.3 删除
    • 7.2 initialization List {} 列表方式初始化
    • 7.3 容器的使用
      • 7.3.1 std::map
        • 插入
        • 查找
        • 删除
      • 7.3.2 std::vector
        • 插入
        • 查找
        • 删除
      • 7.3.3 std::list
        • 插入
        • 查找
        • 删除
  • 八、initializer_list 类
    • 8.1 原理
    • 8.2 常见用法:
      • 8.2.1 普通函数参数
      • 8.2.2 构造器参数
    • 8.3 格式
  • 九、auto 自动类型推导
    • 9.1 注意
    • 9.2 应用
  • 十、declrtype
    • 10.1 推导规则
    • 10.2 应用
  • 十一、仿函数
      • 11.1 operator()
    • 11.2 带状态的 operator()
  • 十二、Lambda
    • 12.1 匿名函数
    • 12.2 Gammar 格式
      • 12.2.1 格式
      • 12.2.2 解释
  • 十三、 enum和enum class(struct)
    • 13.1 C语言与c++:
    • 13.2 C++->C++11:
    • 13.3 问题描述
    • 13.4 作用域
      • 13.4.1 类内的枚举
      • 13.4.2 带作用域的枚举
    • 13.5 指定类型
  • 十四、 assert/static_assert
    • 14.1 assert 运行期断言,它用来发现运行期间的错误
    • 14.2 static_assert(提前判误)
  • 十五、引用
    • 15.1 右值引用定义 (r-value ref) &&
      • 15.1.1 传引用和传指针,传右值引用
        • 15.1.1.1 传引用
        • 15.1.1.2 传指针
        • 15.1.1.3 传右值引用
    • 15.2 const T & 万能常引用
      • 15.2.1 const 一些常见用法解释
    • std::ref
  • 十六、 深赋值与浅赋值
  • 十七、 深拷贝与浅拷贝
  • 十八、 移动构造
    • 18.1 慎用移动
  • 十九、 std::move
    • 19.1 实质
    • 19.2 应用
    • 19.3 移动构造在STL中的应用
  • 二十、 std::function
    • 20.1 含义
    • 20.2 应用
      • 20.2.1 function 作参数类型实现回调
      • 20.2.1.2 function 作类成员实现回调
  • 二十一、 std::bind
    • 21.1 含义
    • 21.2 作用
      • 21.2.1 绑定普通函数与参数及占位
      • 21.2.2 绑定对象与成员及占位
      • 21.2.3 函数重载情形
    • 21.3 多态之 bind +fucntion
      • 21.3.1.可实现多态使用
      • 21.3.2.function 本是不可以包装类成员函数,bind实现类成员函数的绑定, 然后赋给 fucntion 对象,亦即实现了间接性的包装
  • 二十二、 Unordered Contrainer 无序容器
    • 22.1 unordered_map
  • 二十三、 Auto Memeory Manage 自动内存管理
    • 23.1 RAII(Resource Acquisition Is Initialization)
    • 23.2 auto_ptr(deprecated)
    • 23.3 unique_ptr
      • 23.3.1 常用函数
    • 23.4 shared_ptr
      • 23.5 用法
      • 23.5.1 常用函数
    • 23.6 weak_ptr
  • 二十四、 Thread框架
    • 24.1 join 与 detach
    • 24.2 传参方式
    • 24.3 常用函数
    • 24.4 同步之mutex
    • 24.5 volatile
    • 24.6 lock(),unlock()
    • 24.7 try_lock(),unlock()
    • 24.8 std::lock_guard()
    • 24.9 死锁
    • 24.10 std::recursive_mutex()
    • 24.11 同步之std::condition_variable
      • 24.11.1 成员函数
    • 24.12 std::unique_lock()
    • 24.13 std::atomic
    • 24.14 std::call_once
    • 24.15 volatile相关
    • 24.16 异步相关
      • std::future
      • std::promise
      • std::packaged_task
      • std::promise与std::future配合使用
      • std::packaged_task与std::future配合使用
      • 24.17 std::async
      • 语法

一、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 1int 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;
elsethrow 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…: 函数实参

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

  1. 1C语言 从放弃到入门-王桂林-专题视频课程

    <1>C语言 从放弃到入门-1052人已学习 课程介绍         C语言 从放弃到入门 课程收益     所有对C语言有入门恐惧的人. 讲师介绍     王桂林 更多讲师课程     ...

  2. Python语言学习:python语言的特点、入门、基础用法之详细攻略

    Python语言学习:python语言的特点.入门.基础用法之详细攻略 相关内容 Python 基础教程 目录 python语言的特点 python语言的入门 python语言的基础用法 python ...

  3. Git学习记录 力做全网最强入门教程

    目录 Git学习记录 力做全网最强入门教程 什么是GitHub? 什么是Git? Git的配置 Git的安装(只介绍windos操作系统下) Git的配置 至此我们的入门教程到此结束,更新中级教程要等 ...

  4. 2023.2.3,周五【图神经网络 学习记录17】二部图——BiNE算法:显式关系,隐式关系;新的随机游走方式 特点:随机游走次数 是跟节点中心性相关的,在随机游走的过程中 添加一个停止随机游走的概率

    声明:仅学习使用~ 前情提要: 2023.2.2,周四[图神经网络 学习记录16]异构图Graph Embedding算法--GATNE(异构图多属性 多边 类型算法),不建议普通PC跑-PyChar ...

  5. 第031讲:永久存储,腌制一缸美味的泡菜 | 学习记录(小甲鱼零基础入门学习Python)

    (标答出处: 鱼C论坛) <零基础入门学习Python> 测试题: 0.pickle的实质是什么? pickle的实质是利用一些算法,将你的数据对象腌制成二进制文件,存储在磁盘上,也可以放 ...

  6. 第063讲: 论一只爬虫的自我修养11:Scrapy框架之初窥门径 | 学习记录(小甲鱼零基础入门学习Python)

    上一节课我们好不容易装好了 Scrapy,今天我们就来学习如何用好它,有些同学可能会有些疑惑,既然我们懂得了Python编写爬虫的技巧,那要这个所谓的爬虫框架又有什么用呢?其实啊,你懂得Python写 ...

  7. 第023、024讲:这帮小兔崽子汉诺塔 | 学习记录(小甲鱼零基础入门学习Python)

    (标答出处: 鱼C论坛) <零基础入门学习Python> 测试题: 0.使用递归编写一个十进制转换为二进制的函数(要求采用'取2取余'的方式,结果与调用bin()函数一样返回字符串形式) ...

  8. 第019讲:我的地盘听我的 | 学习记录(小甲鱼零基础入门学习Python)

    (标答出处: 鱼C论坛) <零基础入门学习Python> 测试题: 0.下边程序会输入什么? def next():print('我在next()函数里...')pre()def pre( ...

  9. 量化交易学习记录——(一)全局了解|入门

    本人是计算机的,完全的金融小白,所以这里整理的比较基础. 1. 找专业的网站/论坛 首先要了解哪里是专业人士聚集的地方,论坛和网站以及github整理如下: github项目:https://gith ...

  10. 学习记录_C语言打砖块小游戏

    第一次体验到了写代码一上午,修BUG修一天的感觉.但还有一个BUG始终不知道如何解决,那便是如果额外产生的小球触碰到礼盒砖块(会产生额外球),便会在左上角出现隐形球.无奈只好将礼盒砖块改为一个,并将存 ...

最新文章

  1. 046_CSS3动画
  2. flex----导航
  3. elasticsearch id查询_互联网公司中对【Elasticsearch】的真实应用案例
  4. 二叉树题目----5 平衡二叉树 AND 根据二叉树创建字符串
  5. Linux 内核打印级别
  6. [转载]url带中文参数显示乱码的问题
  7. 超详细深度学习debug指南,国外小哥手把手教你如何调试模型 | 附PPT
  8. 网御神州和北京邮电大学成立信息安全联合实验室(2007-07-04)
  9. JQuery杂项方法
  10. drools规则引擎使用文档
  11. js图片添加文字水印并设置水印居中的方法
  12. word页眉页脚设置
  13. Accuracy和Precision的区别
  14. 厦门大学计算机保研学校,厦门大学计算机科学系(专业学位)计算机技术保研夏令营...
  15. 排版——用Markdown写Csdn博客
  16. html显示当前小时分钟秒秋,北语18秋《网页制作》作业1234
  17. “由于没有公钥,无法验证下列签名”解决办法
  18. 网页文字涉及侵权怎么处理
  19. 一文看懂5种ESD防护方法!
  20. 想成为优秀的程序员这些码德不能缺

热门文章

  1. 成功解决 遇到3dmax模型文件出错,找回并修复出错文件
  2. c语言斐波那契数列for循环数组,C语言斐波那契数列的四种实现方式—递归,迭代,数组,队列...
  3. cass有坐标文件生成里程文件_南方CASS里程文件生成
  4. daemontools的安装、示例、简介
  5. html中内部css样式怎么写,HTMLCSS基础-内联样式和内部样式表
  6. windows操作系统启动后,不用输入用户名和密码,自动登录设置
  7. 全球国家或地区 及其 区号
  8. C# sqlsugar依赖引用报错的问题解决
  9. 搜集的一些电压跟随器的问题和回答
  10. ensp VLAN划分