赞
踩
目录
六. emplace_back 的出现和对比分析 push_back接口 emplace_back 是 结合这 可变模板参数出现的
- struct Point
- {
- int _x;
- int _y;
- };
- int main()
- {
- int x1 = 1;
- int x2{ 2 };
- int array1[]{ 1, 2, 3, 4, 5 };
- int array2[5]{ 0 };
- Point p{ 1, 2 };
- // C++11中列表初始化也可以适用于new表达式中
- int* pa = new int[4]{ 0 };
- return 0;
- }

- struct Date {
- int _year;
- int _month;
- int _day;
- };
-
- int main() {
- int a{ 2 }; //支持使用{}的统一初始化了
- vector <int > intv{ 1, 2, 3, 5 };
- vector <Date > datev{ { 2001, 10, 9 }, Date{ 2001, 10, 21 } };
- //上述的这些方式都是支持的了 {} 其实是调用的构造函数
- return 0;
- }
在容器中构造函数中的出现例子:
光光知道还不够, 我们一定要去看看它的底层实现是怎样的, 如下
- namespace tyj {
- template <class T>
- class vector {
- typedef T* iterator;
- typedef const T* const_iterator;
- public:
- //如何进行一个初始化, 范围形式的初始化
- vector(initializer_list<T>& l) {
- _start = new T[l.size()];
- _finish = _start + l.size();
- _endofstorage = _start + l.size();
- iterator sit = _start;
- //然后就是范围形式的构造了
- //如下是方式1: 基于范围的实现
- /*for (auto& e : l) {
- *sit++ = e;
- }*/
- //然后是第二种形式, 使用迭代器进行赋值, 其实也就是上述范围的赋值的底层
- typename initializer_list<T>::iterator lit = l.begin();
- while (lit != l.end()) {
- *sit++ = *lit++;
- }
- }
- //针对这个 operator = 赋值运算符的重载 还是复用上述的构造函数
- vector<T>& operator=(initializer_list<T> l) {
- vector<T> tmp(l);
- std::swap(_start, tmp._start);
- std::swap(_finish, tmp._finish);
- std::swap(_endofstorage, tmp._endofstorage);
- return *this;
- }
- private:
- iterator _start;
- iterator _finish;
- iterator _endofstorage;
- };
- }
-
- int main() {
- //测试上述的东西:
- //断点测试, 进去查看其中的内存即可
- tyj::vector<int> intv = initializer_list<int>{ 1, 2, 3, 4, 5 };
- return 0;
- }

- int main()
- {
- int i = 10;
- auto p = &i;
- auto pf = strcpy;
- map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };
- //map<string, string>::iterator it = dict.begin();
- auto it = dict.begin();
- return 0;
- }
- template<class T, class U>
- auto Add(T&& t, U&& u)
- ->decltype(std::forward<T>(t) +std::forward<T>(u)) {
- return std::forward<T>(t) +std::forward<T>(u);
- }
-
- int main() {
- auto func = [](int a, double b)->decltype(a + b){ return a + b; };
- cout << Add(2, 5);
- while (1);
- return 0;
- }
定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址, 所以本质还是左值.
- int main() {
- int a = 10;
-
- int& b = a; //此处是左值引用
- int&& c = 2; //此处是右值引用
- const int& d = 100; //突然发现此处也是可行的?
- const int& e = a;
- int&& h = std::move(a); //std::move 作用 将左值引用转换为右值引用
- //先引出结论: const 左值引用既可以引用左值也可以引用右值
- //右值引用 就只能引用右值不可以引用左值
- return 0;
- }
注意点: 我们不可以对于右值进行一个取地址, 但是一旦给右值取别名之后, 会导致右值被存储到特定位置,且可以取到该位置的地址,也就是说例如:不能取字面量10的地址,但是rr1引用后,可以对rr1取地址,也可以修改rr1。如果不想rr1被修改,可以用const int&& rr1 去引用,是不是感觉很神奇, 这个了解一下实际中右值引用的使用场景并不在于此,这个特性也不重要。
- int main()
- {
- double x = 1.1, y = 2.2;
- int&& rr1 = 10;
- const double&& rr2 = x + y;
- rr1 = 20;
- rr2 = 5.5; // 报错
- return 0;
- }
左值引用作为参数和返回值完美解决了深拷贝的问题, 但是存在一些情况像是局部对象的返回, 就没办法以左值引用返回, 这个时候需要进行深拷贝, 于是右值引用的出现使得这个问题的解决成为可能, 有没有什么办法可以将局部对象作为返回值的这个深拷贝也优化掉?????
首先移动构造的本质: 本质是一种资源窃取, 资源转移.....
比如 return str; str 如果是一个局部对象的话, 它出不了函数, 一旦函数调用结束, 就会随着栈帧一起释放掉, 但是它的底层存在 char* _str 这样一个字符串数组的成员. 要是一同释放掉着实浪费, 我们是否可以将其利用起来??????
移动构造的本质就是 将 即将返回的局部对象的所有底层的堆区资源进行转移 窃取, 反正函数调用结束你即将消亡, 然鹅我拷贝构造做深拷贝正好需要的也是这个, 于是将这个即将消亡的 str 的 底层的 堆区 资源转移,进行移动构造出新的对象
- void swap(string& s) {
- ::swap(_str, s._str);
- ::swap(_size, s._size);
- ::swap(_capacity, s._capacity);
-
- } //提供swap方法方便转移资源
- string(string&& s)
- :_str(nullptr)
- ,_size(0)
- ,_capacity(0) {
- swap(s); //直接通过交换, 转移财产, 我的全是空, 和你换
- //反正你即将死亡, 不如将你的资源换给我助我快速构造加以利用
- std::cout << "string(string&& s) ---移动构造" << std::endl;
- }
-
- string& operator= (string&& s) {
- swap(s);
- std::cout << "string& operator= (string&& s) ---移动赋值" << std::endl;
- return *this;
- }
-
- tyj::string to_string(int val) {
- tyj::string s;
- bool flag = 0;
- if (val < 0) {
- val *= -1;
- flag = 1;
- }
- while (val) {
- s.push_back(val % 10);
- val /= 10;
- }
- if (flag) s.push_back('-');
- std::reverse(s.begin(), s.end());
- return s; // 返回即将消亡的局部对象, 如何优化掉这个深拷贝?
- }

图解分析, 存在移动构造和不存在移动构造的区别:
移动构造相对于拷贝构造: 比较区别????
移动构造和拷贝构造本质都是构造一个对象: 只是两者采取的构造方式不一样, 拷贝构造的话如果是深拷贝, 也就是底层存在堆区数据, 存在指针, 就需要新开堆区空间, 且需要进行堆区数据的拷贝, 效率低... 移动构造, 我还是需要堆区空间存储数据, 但是我不自己新开辟, 我直接将拷贝对象的堆区资源转移过来成为我的即可.,.... 不需要new 空间 + 数据转移, 效率提高
注意: 移动构造 和 拷贝构造相比, 它的高效仅仅体现在深拷贝 上面, 如果不存在深拷贝. 仅仅只是栈区数据的拷贝, 两者效率是相同的
深拷贝: 存在堆区空间的拷贝.... 也就是存在底层存储数据的空间的拷贝
移动构造高效就高效在了这个底层存储数据空间的获取上面, 不是从新申请空间 + 拷贝数据的方式来获取的, 而是直接的获取对方的现有空间 + 数据
全部代码如下, 可以测试上述推论: 分别测试存在移动语义和不存在的情况看看调用如何??
- namespace tyj {
- class string {
- typedef char* iterator;
- typedef const char* const_iterator;
- public:
- const_iterator begin() const {
- return _str;
- }
- const_iterator end() const {
- return _str + _size;
- }
- iterator begin() {
- return _str;
- }
- iterator end() {
- return _str + _size;
- }
- string(const char* s = "")
- : _size(strlen(s))
- , _capacity(_size)
- , _str(new char[_size]) {
- //std::cout << "string(const char* s) ---构造对象" << std::endl;
- }
- //提供一个swap 函数 一切都是为了方便后序的资源转移拷贝构造等等复用代码
- void swap(string& s) {
- ::swap(_str, s._str);
- ::swap(_size, s._size);
- ::swap(_capacity, s._capacity);
- }
- string(const string& s)
- :_str(nullptr)
- , _size(0)
- , _capacity(0) {
- //复用代码
- string tmp(s._str);
- swap(tmp);
- std::cout << "string(const string& s) --- 深拷贝" << std::endl;
- }
-
- string& operator=(const string& s) {
- //复用拷贝构造代码
- string tmp(s);
- swap(tmp);
- std::cout << "string& operator=(string s) --- 深拷贝" << std::endl;
- return *this;
- }
-
- //然后右值引用出现了, 出现了另外一种方式, 叫做移动构造
- string(string&& s)
- :_str(nullptr)
- ,_size(0)
- ,_capacity(0) {
- swap(s);
- std::cout << "string(string&& s) ---移动构造" << std::endl;
- }
-
- string& operator= (string&& s) {
- swap(s);
- std::cout << "string& operator= (string&& s) ---移动赋值" << std::endl;
- return *this;
- }
-
- void reserve(size_t n) { //扩容
- if (n <= _capacity) return;
- char* pTemp = new char[n];
- _capacity = n;
- memcpy(pTemp, _str, _size + 1); //拷贝_size + 1个过去, 结束字符也拷贝过去
- delete[]_str;
- _str = pTemp;
- }
-
- void push_back(char c) {
- if (_size == _capacity) {
- reserve(_capacity > 0 ? (_capacity << 1) : 8);
- }
- _str[_size++] = c; //放入数据
- _str[_size] = 0; //后序制结束
- }
-
- private:
- size_t _size;
- size_t _capacity;
- char* _str;
- };
-
- //来一个函数, 方便测试右值引用使用案例
- tyj::string to_string(int val) {
- tyj::string s;
- bool flag = 0;
- if (val < 0) {
- val *= -1;
- flag = 1;
- }
- while (val) {
- s.push_back(val % 10);
- val /= 10;
- }
- if (flag) s.push_back('-');
- std::reverse(s.begin(), s.end());
- return s;
- }
- }
-
- int main() {
- tyj::string s1 = tyj::to_string(123456);
- tyj::string s2;
- s2 = tyj::to_string(23456);
- return 0;
- }

就连 STL的 push_back 等等这种接口上都是同样增添了右值引用版本的:
万能引用: 就是 既可以引用左值 也可以引用右值 模板中的&& 万能引用
为了引出完美转发 首先先看如下的一段代码
- void f(int& a) {
- std::cout << "左值引用" << endl;
- }
-
- void f(const int& a) {
- std::cout << "const 左值引用" << endl;
- }
-
- void f(int&& a) {
- std::cout << "右值引用" << endl;
- }
-
- void f(const int&& a) {
- std::cout << "const 右值引用" << endl;
- }
-
- template <class T>
- void PerfectForward(T&& t) {
- f(t);
- }
-
- int main() {
- PerfectForward(2);
- int a = 10;
- PerfectForward(a);
- PerfectForward(move(a));
- return 0;
- }

- void PerfectForward(T&& t) {
- //先尝试一下不是完美转发
- //f(t);
- //然后进行完美转发
- f(std::forward<T>(t)); //转发之后效果就恢复正常了
- }
- template<class T>
- struct ListNode
- {
- ListNode* _next = nullptr;
- ListNode* _prev = nullptr;
- T _data;
- };
-
- //如下是实际的测试案例
- template<class T>
- class List
- {
- typedef ListNode<T> Node;
- public:
- List()
- {
- _head = new Node; //搞一个虚拟头部
- _head->_next = _head;
- _head->_prev = _head; //双向循环
- }
- void PushBack(T&& x)
- {
- //Insert(_head, x);
- Insert(_head, std::forward<T>(x)); //完美转发
- }
- void PushFront(T&& x)
- {
- //Insert(_head->_next, x);
- Insert(_head->_next, std::forward<T>(x));
- }
- void Insert(Node* pos, T&& x)
- {
- Node* prev = pos->_prev;
- Node* newnode = new Node;
- newnode->_data = std::forward<T>(x); // 关键位置
- // prev newnode pos
- prev->_next = newnode;
- newnode->_prev = prev;
- newnode->_next = pos;
- pos->_prev = newnode;
- }
- void Insert(Node* pos, const T& x)
- {
- Node* prev = pos->_prev;
- Node* newnode = new Node;
- newnode->_data = x; // 关键位置
- // prev newnode pos
- prev->_next = newnode;
- newnode->_prev = prev;
- newnode->_next = pos;
- pos->_prev = newnode;
- }
- private:
- Node* _head;
- };
- int main()
- {
- List<tyj::string> lt;
- lt.PushBack("1111");
- lt.PushFront("2222");
- while (1);
- return 0;
- }
-
- //上述所有的 传入 && 右值引用作为参数的地方后序进一步传参全部需要使用forward<>()完美转发
- //完美转发保持之前原有的类型属性不变, 如果不使用完美转发效果就是后序全部变成左值引用退化了
- //可以取地址的就是左值了, 不可以取地址的才是右值, 右值一旦被变量接收其实也就退化成左值了
- //如果想要继续保持右值的属性就需要完美转发

- // Args是一个模板参数包,args是一个函数形参参数包
- // 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
- template <class ...Args>
- void ShowList(Args... args)
- {}
模板参数包的简单使用. 第一种解包方式, 递归解包
- //设置递归终点, 当参数包解包完全, 适配空包
- void ShowList() {
- cout << endl;
- }
- //递归形式调用解包, 每一次解出一个参数
- template<class T, class ...Args >
- void ShowList(T& val, Args... args) {
- cout << val << endl;
- ShowList(args...);
- }
- int main() {
- ShowList("dsadsa", 2, 5, 6, "edsad");
- return 0;
- }
- template<class T>
- int PrintArg(T& val) {
- cout << val << endl;
- return 0;
- }
- template<class ...Args>
- void ShowList(Args... args) {
- int arr[] = { PrintArg(args)... };
- }
- int main() {
- ShowList(1, 43, 6, 7, 8, "dfsads", "dsaw", 'a');
- return 0;
- }
- int main()
- {
- // 下面我们试一下带有拷贝构造和移动构造的tyj::string,来试试
- // 我们会发现其实差别也不到,emplace_back是直接构造了,push_back
- // 是先构造,再移动构造,其实效率也还好, 差别不算很大
- std::list< std::pair<int, tyj::string> > mylist;
- mylist.emplace_back(10, "sort");
- mylist.emplace_back(make_pair(20, "sort"));
- mylist.push_back(make_pair(30, "sort"));
- mylist.push_back({ 40, "sort" });
- return 0;
- }
如下, 先进行一个简单的使用
- struct Cars {
- int carnum;
- int price;
- string name;
- };
-
- struct cmp {
- bool operator()(Cars& c1, Cars& c2) {
- return c1.price < c2.price;
- }
- };
-
- bool cmp2(const Cars& c1, const Cars& c2) {
- return c1.price < c2.price;
- }
- int main() {
- auto fun = [] {}; //这个是最为简单的lambda表达式啥都不干
- fun(); //调用, 使用方式像极了无参仿函数调用
- auto add = [](double a, double b)->double { return a + b; };
- cout << add(2.7, 3.7) << endl;
- //然后是常用方式: 代替 仿函数使用
- Cars cars[] = {
- {100, 150000, "长城"}
- , {55, 20000, "宝马摩托"}
- , {455, 1000, "小电瓶"}
- , {1000, 500, "自行车"}};
- //形式1:
- sort(cars, cars + sizeof(cars) / sizeof(Cars)
- , [](Cars& c1, Cars& c2)->bool {return c1.price < c2.price; });
- //形式2:
- sort(cars, cars + sizeof(cars) / sizeof(Cars), cmp()); //传入匿名可调用对象
- //形式3:
- sort(cars, cars + sizeof(cars) / sizeof(Cars), cmp2); //传入函数指针
- return 0;
- }

针对对于自定义对象的 sort 还有一点点小小的技巧, 可以在我们需要 sort 的自定义类中去重载一下operator < 函数, 直接不需要在自己传入排序规则了.......
why??? 上述 重载一下 operator < 就可以达到重建排序规则 () 的效果
- struct Cars {
- int carnum;
- int price;
- string name;
-
- bool operator<(Cars& c) const {
- return price < c.price;
- }
- };
- int main() {
- Cars cars[] = {
- {100, 150000, "长城"}
- , {55, 20000, "宝马摩托"}
- , {455, 1000, "小电瓶"}
- , {1000, 500, "自行车"} };
- sort(cars, cars + sizeof(cars) / sizeof(Cars));
- return 0;
- }

- //函数指针
- int add1(int a, int b) {
- return a + b;
- }
-
- //仿函数
- struct Add {
- int operator()(int a, int b) {
- return a + b;
- }
- int a, b;
- };
- int main() {
- auto add2 = [](int a, int b){ return a + b; }; //当然可以在()->指定后置返回类型
- //auto add2 = [](int a, int b)->int { return a + b; };
- function<int(int, int) > func1 = add1; //函数名
- function<int(int, int) > func2 = Add(); //函数对象
- function<int(int, int) > func3 = add2; //lambda表达式
- std::cout << func1(3, 5) << std::endl;
- std::cout << func2(3, 5) << std::endl;
- std::cout << func3(3, 5) << std::endl;
- while (1);
- return 0;
- }

包装器的好处???? 统一了可调用对象的类型, 并且指定了参数和返回值类型
1. 简化了函数指针这样的复杂指针的使用, 函数指针复杂难以理解
2. 方便了作为参数时候的传入
3. 仿函数是一个类名没有指定参数和返回值需要知道就需要去看这个operator () 重载获取
4. lambda 在语法层, 看不到类型, 底层存在类型, 但是也是lambda_uuid, 也很难看我觉得function 出现的 最最重要的原因就是有了一个确切的类型, 使用简单方便,
解决函数指针使用复杂的问题, 解决仿函数不能指定参数类型的问题, 要知道参数类型还要跑去看哪个 operator() 以及解决 lambda没有具体类型的问题.
实际案例:
根据 逆波兰表示法,求表达式的值。
有效的算符包括
+
、-
、*
、/
。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
未使用 function代码:
- class Solution {
- public:
- int evalRPN(vector<string>& tokens) {
- stack<int> st;
- for(int i=0;i<tokens.size();i++){
- if(tokens[i]=="+"||tokens[i]=="-"||tokens[i]=="*"||tokens[i]=="/"){
- int rhs=st.top();
- st.pop();
- int lhs=st.top();
- st.pop();
- switch(tokens[i][0]){
- case '+':st.push(lhs+rhs);break;
- case '-':st.push(lhs-rhs);break;
- case '*':st.push(lhs*rhs);break;
- case '/':st.push(lhs/rhs);break;
- }
- continue;
- }
- st.push(stoi(tokens[i]));
- }
- //然后就是最后的结果了
- return st.top();
- }
- };

使用 function 的代码:
- class Solution {
- //使用包装器进行复用??? 如何利用包装器??
- //需要的是 function 和 对应的 op 对应起来...
- //如何对应 使用的就是map 对应 map<string, function<int(int, int) > > opmap
- //逆波兰表达式: 左 右 op
- //遇到 op 的时候 说明前面的就是 l + r
- //每一个运算结果需要重新入栈
- public:
- int evalRPN(vector<string>& tokens) {
- stack<int > numst;
- map<string, function<int(int, int) > > opmap = {
- {"+", [](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;}}
- };
- for (auto& e : tokens) {
- if (e == "+" || e == "-" || e == "*" || e == "/") {
- int r = numst.top(); numst.pop();
- int l = numst.top(); numst.pop(); //先提取的是r 后 l
- numst.push(opmap[e](l, r));
- } else {
- numst.push(stoi(e));
- }
- }
- return numst.top();
- }
- };

简单的用起来
- int main() {
- size_t n = 100;
- thread t1([n]{
- for (size_t i = 0; i < n; i += 2) {
- cout << i << endl;
- }
- });
- cout << t1.get_id() << endl; //线程id
- thread t2([n]{
- for (size_t i = 1; i < n; i += 2) {
- cout << i << endl;
- }
- });
- cout << t2.get_id() << endl; //线程id
- t1.join();
- t2.join(); //主线程阻塞等待子线程的死亡
- while (1); //等待他们结束
- return 0;
- }

- void TFun() {
- cout << "函数指针" << endl;
- }
- struct TF {
- void operator()() {
- cout << "函数对象" << endl;
- }
- };
- int main() {
- thread t1(TFun); //传入函数指针
- TF tf;
- thread t2(tf); //可调用对象(仿函数)
- thread t3([]() {cout << "Lambda" << endl; });
- t1.join();
- t2.join();
- t3.join(); //join 主线程挂起等待三个线程结束返回
- return 0;
- }

线程函数的参数是以值拷贝的方式拷贝到线程栈空间中的,因此:即使线程参数为引用类型,在 线程中修改后也不能修改外部实参,因为其实际引用的是线程栈中的拷贝,而不是外部实参。 (线程函数参数传入是以值拷贝的形式拷贝到栈空间中的, 所以既是是引用类型, 在线程中修改后外部实参也是无法修改的) 如何处理这个问题, 如下代码解释
std::ref(); 使用这个函数转换之后传入的线程函数参数才是真正的引用, 线程中改变, 外面也会改变
- class Fun {
- public:
- void operator()() {
- cout << "operator()" << endl;
- }
- };
-
- void ThreadFunc1(int& x)
- {
- x += 10;
- }
- void ThreadFunc2(int* x)
- {
- *x += 10;
- }
-
- int main() {
- //测试一波:
- int a = 10;
- thread t1(ThreadFunc1, a); //传入a
- cout << a << endl; //?? a 是否改变?
- //上述发现 a 没有改变
- //如何可以使得传入的数据不需要进行拷贝, 而是原有数据?
- 如果想要通过形参改变外部实参时,必须借助std::ref()函数
- thread t3(ThreadFunc1, std::ref(a));//才不会传入拷贝本
- cout << a << endl;
- thread t2(ThreadFunc2, &a); //这样看一看???
- cout << a << endl; //a改变了, 因为这个传入的是地址进去
- t1.join();
- t2.join();
- return 0;
- }

多线程最主要的问题是共享数据带来的问题(即线程安全)。如果共享数据都是只读的,那么没问题,因为只读操作不会影响到数据,更不会涉及对数据的修改,所以所有线程都会获得同样的数据。但是,当一个或多个线程要修改共享数据时,就会产生很多潜在的麻烦。
//多线程对于共享数据的写操作带来的问题...
- unsigned long num = 0L; //先定义全局的数据
- void tf(size_t n) {
- for (size_t i = 0; i < n; ++i) {
- num += 1;
- }
- }
-
- int main() {
- thread t1(tf, 10000000);
- thread t2(tf, 10000000);
- t1.join();
- t2.join();
- cout << num << endl;
- return 0;
- }
解决上述问题的方式1: 在 C++98 中采取的是加锁的方式实现避免函数的重入问题,
lock();
操作临界资源 (写入操作)
unlock();
- unsigned long num = 0L; //先定义全局的数据
- mutex mtx;
- void tf(size_t n) {
- for (size_t i = 0; i < n; ++i) {
- mtx.lock();
- num += 1;
- mtx.unlock();
- }
- }
-
- int main() {
- thread t1(tf, 10000000);
- thread t2(tf, 10000000);
- t1.join();
- t2.join();
- cout << num << endl;
- return 0;
- }

加锁确实是可以解决上述的问题, 但是不停的解锁解锁, 效率会变得特别低, 时间消耗也会大大增加, 不停的加锁解锁, 虽然也解决了问题, 保护了临界资源.. 但是程序运行时延性大大增加, 而且对于锁控制不好还会死锁, 于是C++11 搞出来一个原子操作。所谓原子操作:即不可被中断的一个或一系列操作,C++11引入 的原子操作类型,使得线程间数据的同步变得非常高效。
- atomic_long num{ 0 };//定义全局的原子操作数据
-
- void tf(size_t n) {
- for (size_t i = 0; i < n; ++i) {
- num += 1;
- }
- }
- int main() {
- thread t1(tf, 10000000);
- thread t2(tf, 10000000);
- t1.join();
- t2.join();
- cout << num << endl;
- return 0;
- }
有了原子操作数据, 确实针对这些数据的操作不再需要加锁保护了, 但是如果是一段代码段的原子操作, 就还是不得不使用锁来实现, 但是只要设计到锁就可能发生死锁, C++11为了预防死锁, C++11采用RAII的方式对锁进行了封装,即lock_guard和unique_lock。
- // RAII
- namespace tyj
- {
- template<class Lock>
- class lock_guard
- {
- public:
- lock_guard(Lock& lock)
- :_lock(lock)
- {
- _lock.lock();
- cout << "加锁" << endl;
- }
-
- /*void lock()
- {
- _lock.lock();
- }
-
- void unlock()
- {
- _lock.unlock();
- }*/
- //对于lock_guard是没有上述操作的, 它仅仅只是做垃圾回收
- //出作用域自动回收锁, 调用析构解锁
- ~lock_guard()
- {
- _lock.unlock();
- cout << "解锁" << endl;
- }
-
- lock_guard(const lock_guard<Lock>& lock) = delete;
-
- private:
- Lock& _lock;
- };
- }

要求 : 支持两个线程交替打印,一个打印奇数,一个打印偶数
- int main() {
- mutex mtx; //定义锁 为后面的完成要求做准备
- bool flag = 1;//flag = 1 打印偶数 flag = 0 打印奇数 配合 condion_variable使用
- condition_variable _cond; //定义条件变量为后序相互耦合关联式打印埋伏笔
- thread t1([&](){
- unique_lock<mutex> _lock(mtx);
- int i = 0;
- while (i < 100) {
- while (!flag) _cond.wait(_lock);//不满足flag 一直等
- //说明满足了
- cout << "i: " << i << endl;
- flag = 0; //修改让t2去打印
- _cond.notify_one();//唤醒t2打印奇数了
- i += 2;
- }
- });
-
- thread t2([&](){
- unique_lock<mutex> _lock(mtx);
- int j = 1;
- while (j < 100) {
- while (flag) _cond.wait(_lock);//满足flag 说明这个时候在打印偶数
- //说明满足了
- cout << "j: " << j << endl;
- flag = 1; //修改让t2去打印
- _cond.notify_one();//唤醒t2打印奇数了
- j += 2;
- }
- });
- t1.join();
- t2. join();
- return 0;
- }

- 首先本章介绍了初始化参数列表{} 进行统一的初始化
- {} 的本质是一个类型 叫做 initializer_list , 支持使用{} 构造的本质是支持传入initializer_list做参数的构造函数
- 然后引入了右值引用, 可以取地址的是左值, 右值是不可以去地址的值, 一旦给右值取别名, 右值就会退化, 就会分配空间 + 地址 退化为左值
- 然后通过右值引用引出移动构造, 移动构造相比拷贝构造好处体现在深拷贝上面, 他和深拷贝不同的是不需要重新开底层的存储空间 + 转移数据, 直接窃取右值的底层空间
- 模板右值引用: 万能引用, 引用接收之后所有的右值会退化为左值, 想要保持住右值属性不退化, 需要进行 std::forward<>()完美转发, 保持右值属性
- 然后是 lambda表达式的引出, [捕获列表](参数列表)->后置返回类型{函数体} 且lambda表达式的底层处理就是 类的可调用对象 operator() 运算符重载
- function 包装器 对于 函数指针 仿函数 lambda表达式的统一封装.... 包装: 好处, 使用起来更加方便, 指定好了参数和返回值类型, 作为参数传入也更加方便灵活...
- thread 线程类库, C++11 支持的线程库, 参数的传入以值拷贝形式, 要想传入的是真正的引用 必须进行 std::ref()处理
- 原子操作: 创建了一套原子操作数据类型 atomic_long 等等以atomic开头的支持原子操作的数据类型, 相比 使用mutex 更加高效, 且不会死锁
- 但是由于对于代码段的原子操作, 原子操作的数据类型 无能为力, 只能使用 mutex, 使用锁为了避免死锁, C++11 产生了 锁的管理模板类 unique_lock 和 lock_guard 进行管理锁, 在 对象结束的时候调用析构解锁, 不至于一直死锁
- 因为一直使用锁, 效率极低, 所以 可以使用 condition_variable 配合锁使用完成一些特殊的要求, 以及提高效率, 不至于让系统一直不停的加锁解锁, 因为加锁解锁 耗费CPU资源
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。