【C++11】C++11相关特性的简单介绍(二)

【C++11】C++11相关特性的简单介绍(二)


接上节:C++11相关特性介绍(一)

编译环境:vs2013


一、可变参数模板

1. 可变模板参数

在C++98中,类模板和函数模板中的参数个数是固定的,这就带来了一定的限制,在C++11中增加了可变参数模板,可以创建接受可变参数的函数模板和类模板。

//...Args:类型参数包
template<class ...Args>
//声明一个参数包Args... args,参数包中可以包含0到任意个模板参数
void ShowList(Args... args)
{}
//函数
ShowList(10);
ShowList(10,12.23);
ShowList(10,12.23,"hello");

参数args前面有省略号,故其为一个可变模版参数,把带省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模版参数。
那如何将参数包中的参数进行展开呢?

1. 参数包的展开

(1)递归方式展开参数包

//递归的结束出口函数
template<class T>
void ShowList(const T& data)
{
	cout << data << endl;
}
//函数的参数展开
template<class T,class ...Args>
void ShowList(T value, Args... args)
{
	cout << value << " ";
	ShowList(args...);
}

在这里插入图片描述
(2)逗号表达式展开参数包

//处理参数包中每一个参数的函数
template<class T>
void Print(T t)
{
	cout << t << " ";
}
//逗号表达式 (Print(args), 0),先执行表达式 Print(args) ,再得到逗号表达式的结果0
template<class ...Args>
void showlist(Args... args)
{
	int array[] = { (Print(args), 0)... };
	cout << endl;
}

逗号表达式 (printarg(args),0),
先执行printarg(args),再得到逗号表达式的结果0,同时还用到了C++11的另外一个特性——初始化列表,通过初始化列表来初始化一个变长数组array,{(printarg(args), 0)…}将会展开成((printarg(arg1),0), (printarg(arg2),0),(printarg(arg3),0), etc… ),最终会创建一个元素值都为0的数组int array[sizeof…(Args)]。
在创建数组的过程中,逗号表达式会先执行逗号表达式前面的部printarg(args)打印出参数,也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包。

在这里插入图片描述

二、lambda表达式

1. lambda表达式的引入

在进行排序的时候,若想对一个集合中元素进行排序,std::sort可进行实现。
如下:

//sort头文件
#include<algorithm>
//less和greater头文件,实际为类模板,在类中将()进行重载了
#include<functional>

int main()
{
	int array[] = { 4, 1, 8, 5, 3, 7, 0, 9, 2, 6 };
	//默认按照升序的方式进行排序
	std::sort(array, array + sizeof(array) / sizeof(array[0]));
	//greater按照升序的方式进行排序
	for (auto e : array)
	{
		cout << e << " ";
	}
	cout << endl;

	std::sort(array, array + sizeof(array) / sizeof(array[0]), greater<int>());
	for (auto e : array)
	{
		cout << e << " ";
	}
	cout << endl;
	return 0;
}

在这里插入图片描述
对于自定义类型,得用户自己定义一份排序算法。
如下:

struct good
{
	string _name;//商品名称
	double _price;//商品价格
	int _eva;//商品评价

	good(const char* str, double price, int eva)
		:_name(str)
		, _price(price)
		, _eva(eva)
	{}
};
//按照商品价格对商品进行升序排序
struct ComPriceLess
{
	bool operator()(const good& g1, const good& g2)
	{
		return g1._price < g2._price;
	}
};
//按照商品价格对商品进行降序排序
struct ComPriceGreater
{
	bool operator()(const good& g1, const good& g2)
	{
		return g1._price > g2._price;
	}
};

在这里插入图片描述
如上,我们可以看到,对于自定义类型,根据排序需求的不同,我们得实现很多的类,我们若想以商品中的评价来进行升降排序的话,又得实现两份类,这在代码上就显得十分的多余和不便。
而lambda表达式就会大大的简化此步骤。
用lambda表达式实现上述思想如下:

int main()
{
vector<good> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "鸭梨", 2.2,
3 }};

sort(v.begin(), v.end(), [](const good& g1, const good& g2){
return g1._price < g2._price; });
sort(v.begin(), v.end(), [](const good& g1, const good& g2){
return g1._price > g2._price; });

sort(v.begin(), v.end(), [](const good& g1, const good& g2){
return g1._eva< g2._eva; });
sort(v.begin(), v.end(), [](const good& g1, const good& g2){
return g1._eva > g2._eva; });
}

可以看到,lambda表达式实际上是一个匿名函数。

2. lambda表达式的形式

在这里插入图片描述

  1. 捕获列表:捕捉上下文中的变量供lambda函数使用,以及使用的方式是传值还是引用,[]表示lambda表达式的起始

    • [var]:值传递方式捕获变量var
    • [=]:值传递方式捕获所有父作用域中变量,包括this指针
    • [&var]:引用传递方式捕获变量var
    • [&]:引用传递方式捕获所有父作用域中的所有变量,包括this指针
    • [this]:值传递方式捕获当前的this指针

注意:

  • 父作用域:表示包含lambda表达式的语句块,捕获的变量都是在父作用域中表达式前的变量
  • 捕获列表也可由多个捕获项组成:
    [=,&a,&b]:引用传递方式捕捉变量a和b,值传递方式捕捉其他所有变量
    [&,a,this]:值传递方式捕获this和a,引用方式捕获其他所有变量
  • 捕获列表不允许变量重复捕获,否则编译错误:
    [=,a]:=值传递方式捕获所有变量,重复捕获变量a
    在这里插入图片描述
    lambda表达式实际上可解释为无名函数,无法直接调用,若想要调用,借助auto将其赋值给一个变量,从而进行调用。
  • 在块作用域以外的lambda函数捕获列表必须为空
  • lambda表达式之间不可赋值
  1. 参数列表:与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略
  2. mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)
  3. 同函数的返回值类型,lambda表达式的返回值类型
  4. 同函数体的实现

3. lambda表达式的说明

1. lambda表达式之间不可赋值👇

#include<iostream>
using namespace std;

void(*pf)();
int main()
{
	auto f1 = []{cout << "hello world" << endl; };
	auto f2 = []{cout << "hello world" << endl; };
	//lambda表达式拷贝构造一个新对象并调用
	auto f3(f2);
	f3();
	//lambda表达式赋值给相同类型的函数指针并调用
	pf = f2;
	pf();
	
	//将f2赋值给f1
	//f1 = f2;
	
	system("pause");
	return 0;
}

在这里插入图片描述
定义两一样的lambda表达式,可以看到,当用f2拷贝构造f3和将其赋值给同类型的函数指针时,都执行了表达式的执行体中打印语句。那f1与f2看起来无任何区别,二者可以互相赋值吗?
在这里插入图片描述
可以看到程序报错,两者并不可以相互赋值。从报错中也可以看到大概意思就是没有赋值运算符重载函数,作用域限定符也侧面说明了lambda表达式可能是一个类。
再通过一个例子来看一下其底层实现。

class Rate
{
public:
	Rate(double rate) 
		: _rate(rate)
	{}
	double operator()(double money, int year)
	{
		return money * _rate * year;
	}
private:
	double _rate;
};
int main()
{
	// 函数对象进行调用
	double rate = 0.5;
	Rate r1(rate);
	r1(1000, 3);

	// lamber表达式来进行调用
	auto r2 = [=](double monty, int year)->double
	{
		return monty*rate*year;
	};
	r2(1000, 3);
	return 0;
}

反汇编:
在这里插入图片描述
在这里插入图片描述
由程序反汇编可以看到,lambda表达式的使用和仿函数一样。
实际上,在用户定义的lambda表达式,C++编译器都会将lambda表达式转化为对应的类,然后给该类添加构造方法和()的重载函数,让该类的对象可以像函数一样的方式来进行使用。
其中,lambda表达式的捕获列表,实际是生成的类的构造函数的参数列表,而lambda表达式的形参列表和实现体就是用来实现operator()。

	auto f1 = []{cout << "hello world" << endl; };
	auto f2 = []{cout << "hello world" << endl; };

故,虽然两lambda表达式形式上是一样的,实际上两个lambda表达式编译器是生成了两个不同的类,只是两个类中()方法的实现是一样的,两个不同类对象之间进行赋值,当然也就不可以了。

2. 捕获列表类说明

在lambda表达式的捕获列表中,若是以名字的方式捕获,捕获列表中的内容将来就是lambda表达式对应生成类的成员变量;若是=或者&捕获父作用域中所有变量,而lambda表达式中这些变量一个都未使用,则生成的类就是一个空类,而若使用了哪些变量,哪些变量就是生成类中的成员变量。
在这里插入图片描述

三、function包装器

1. function包装器场景引入

function包装器,也称适配器,C++中的function本质是一个类模板,也是一个包装器。

可调用对象:

  1. 函数指针变量
  2. 函数对象(仿函数对象)
  3. lambda表达式

解决场景:

//类模板
template<class F,class T>
T Fun(F f, T x,T y)
{
    static int count = 0;
	cout << "count:" << ++count << endl;
	cout << "count:" << &count << endl;
	return f(x, y);
}
//函数:实现两数相加
int add(int a, int b)
{
	return a + b;
}
//类:()重载实现两数相减
class sub
{
public:
	int operator()(int m,int n)
	{
		return m - n;
	}
};
int main()
{
	//函数指针
	cout << Fun(add, 10, 20) << endl;
	
	//函数对象
	cout << Fun(sub(), 30, 40)<<endl;

	//lambda表达式
	cout << Fun([](int c, int d)->int{return c / 2 + d / 2; }, 20, 20) << endl;

	system("pause");
	return 0;
}

反汇编:
在这里插入图片描述
模板虽然可以实现代码的重用和简化,但每次都得根据传入参数的不同来实例化一份处理对应数据的函数模板。实际上,每次传入不同的参数,都得生成一份,多份的模板会造成可执行程序非常的大,从而使得效率低下。
这时候就需要包装器来解决。

2. function包装器形式及使用

std::function 头文件 <functional>
//类模板原型:
template<class T> function;

template <class Ret,class... Args>
class function<Ret(Args...)>
Ret:被调用函数的返回值类型
Args...:被调用函数的形参

function包装器的使用:

#include<functional>
int main()
{
	//函数指针
	int a = 0;
	a = Fun(add, 10, 20);
	cout << a << endl;
	
	//函数对象
	a = Fun(sub(), 30, 40);
	cout << Fun(sub(), 30, 40)<<endl;

	//lambda表达式
	a = Fun([](int c, int d)->int{return c / 2 + d / 2; }, 20, 20);
	cout << a << endl;

	cout << endl;
	//function包装器的使用
	int b = 0;
	//f为包装器的一个对象
	function<int(int, int)> f(add);
	b = Fun(f,10, 20);
	cout << b << endl;

	f = sub();
	b = Fun(f, 30, 40);
	cout << b << endl;

	f = [](int c, int d)->int{return c / 2 + d / 2; };
	b = Fun(f, 20, 20);
	cout << b << endl;

	system("pause");
	return 0;
}

反汇编:
在这里插入图片描述
在这里插入图片描述
当使用了function包装器后,实例化的类只有一份,这样,就不会实例化出多份。
那当返回的函数是类中的普通成员函数和静态成员函数的时候该如何包装。

class Add
{
public:
	static int add1(int a, int b)
	{
		return a + b;
	}
	double add2(double a, double b)
	{
		return a + b;
	}
};
int main()
{
	function<int(int, int)> ff = Add::add1;
	ff(10, 20);
	//add1与add2函数类型不一样,编译报错,非静态成员里面有this指针
	//ff = Add::add2;

	function<double (Add, double , double )> pp = &Add::add2;
	//包装非静态成员方法的时候,函数名前得加上&
	pp(Add(),10.0, 20.0);
	//表示用Add()对象调用函数里面的函数add2

	system("pause");
	return 0;
}

3. bind参数的绑定

可以将bind函数看作是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。

std::bind 头文件 #include<functional>
// 原型如下:
template <class Fn, class... Args>
bind (Fn&& fn, Args&&... args);

template <class Ret, class Fn, class... Args>
bind (Fn&& fn, Args&&... args);

bind的一般形式:auto newCallable = bind(callable,arg_list)

  1. newCallable:本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给
    定的callable的参数。当我们调用newCallable时,newCallable会调用callable,并将
    arg_list中的参数传给它。
  2. arg_list中的参数可能包含形如_n的名字,其中n是一个整数,这些参数是“占位
    符”,表示newCallable的参数,它们占据了传递给newCallable的参数的“位置”。数值
    n表示生成的可调用对象中参数的位置,_1为newCallable的第一个参数,_2为第二个参数,以此类推。
int Plus(int a, int b)
{
	return a + b;
}
class Sub
{
public:
	int sub(int a, int b)
	{
		return a - b;
	}
};
int main()
{
	//绑定函数为普通函数
	//绑定函数Plus,参数由绑定预留的第一和第二个参数来进行传递
	function<int(int, int)> f1 = bind(Plus, placeholders::_1, placeholders::_2);
	//传递1和2
	//placeholders::_1位置就是1,将来传递给Plus函数的第一个参数
	//placeholders::_2位置就是2,将来传递给Plus函数的第二个参数
	cout << f1(1, 2) << endl;

	//f2类型为:function<int(int, int)>
	auto f2 = bind(Plus, 3, 4);
	cout << f2(3, 4) << endl;
	cout << f2() << endl;

	//绑定的函数是类成员函数
	Sub s;
	function<int(int, int)> f3 = bind(&Sub::sub, s, placeholders::_1, placeholders::_2);
	cout << f3(7, 8) << endl;

	//将预留的参数位置进行调换
	function<int(int, int)> f4 = bind(&Sub::sub, s, placeholders::_2, placeholders::_1);
	//9是第二个参数,将来传递给类成员函数sub的第二个参数
	//10是第一个参数,将来传递给类成员函数sub的第一个参数
	cout << f4(9, 10) << endl;

	system("pause");
	return 0;
}

在这里插入图片描述

四、线程库

1. 标准库线程库的引入

在C++11之前,涉及到多线程问题,都是和平台相关的,比如windows和linux下各有自己的接口,这使得代码的可移植性比较差。
C++11中最重要的特性就是对线程进行支持了,使得C++在并行编程时不需要依赖第三方库,而且在原子操作中还引入了原子类的概念。定义在< thread >头文件。

thread文档

函数函数说明
thread()创建线程,未关联线程函数,未启动
get_id()获取线程id
joinable()线程是否正在执行,表示一个正在执行的线程
join()阻塞线程,线程结束后,该线程继续执行
detach()创建线程对象后立即调用,将被创建线程与线程对象分离开,分离的线程变为后台线程

2. 线程创建执行

当创建一线程,但是不给其执行内容,即该线程要完成的任务,则该线程是未启动的。
在这里插入图片描述
创建好线程,给出线程的执行内容,线程就会和主线程一快执行。

线程函数一般情况下提供方式:

  1. 函数指针
  2. lambda表达式
  3. 函数对象
#include<thread>
// 函数指针
void ThreadFun(int a)
{
	cout << "thread1" << endl;
}
// 函数对象,仿函数
class Funal
{
public:
	void operator()()
	{
		cout << "thread3" << endl;
	}
};
int main()
{
	//线程执行函数是函数指针
	thread t1(ThreadFun, 10);
	//执行函数是lambda表达式
	thread t2([]{cout << "thread2" << endl; });
	//执行函数是函数对象
	Funal f;
	thread t3(f);

	//确保线程在主线程结束前结束
	t1.join();
	t2.join();
	t3.join();

	system("pause");
	return 0;
}

在这里插入图片描述

  • thread类是防拷贝的,不允许拷贝构造以及赋值,但是可以移动构造和移动赋值,即将一个线程对象关联线程的状态转移给其他线程对象,转移期间不影响线程的执行
  • jionable()函数判断线程是否是有效的
  • 采用无参构造函数构造的线程对象、线程对象的状态已经转移给其他线程对象、线程已经调用jion或者detach结束的线程都是无效的

3. 线程函数参数

线程函数的参数是以值拷贝的方式拷贝到线程栈空间中的,因此:即使线程参数为引用类型,在线程中修改后也不能修改外部实参,因为其实际引用的是线程栈中的拷贝,而不是外部实参。

#include<thread>
void ThreadFun1(int& a)
{
	a += 10;
}
void ThreadFun2(int* a)
{
	*a += 10;
}
int main()
{
	int x = 10;
	//线程t1修改x,不会影响外部的实参,其为线程栈中的一份拷贝,打印x=10
	thread t1(ThreadFun1, x);
	t1.join();
	cout << x << endl;

	//地址的拷贝
	thread t2(ThreadFun2, &x);
	t2.join();
	cout << x << endl;

	//std::ref():通过形参改变外部的实参
	thread t3(ThreadFun1, std::ref(x));
	t3.join();
	cout << x << endl;

	system("pause");
	return 0;
}

在这里插入图片描述

五、原子性操作库

多线程中由于存在多个线程同时去对某一数据进行操作,当是读取数据时,不会对数据进行修改,不会影响结果,而当多个线程同时对某一共享数据进行修改操作时,就会产生线程安全的问题。

#include<iostream>
using namespace std;
#include<mutex>
#include<thread>
unsigned long sum = 0;
void fun(size_t num)
{
	for (size_t i = 0; i < num; ++i)
	{
		sum++;
	}
}
int main()
{
	cout << "Thread Before: sum=" << sum << endl;

	thread t1(fun, 100000000);
	thread t2(fun, 100000000);

	t1.join();
	t2.join();

	cout << "Thread After: sum=" << sum << endl;

	system("pause");
	return 0;
}

在这里插入图片描述
我们想的结果是,两个线程分别对sum进行加100000000次,最终结果应该是200000000,而运行结果每次都是不一样的。
这是因为对于sum++的这个过程,首先是从内存中读取数据到寄存器,然后数据在寄存器中进行运算,最后再将结果数据加载到内存中,对于这个过程,两个线程所处的状态都是随机的,可能同时也可能一前一后等,即sum++的操作并非是原子操作和线程安全的。

原来我们解决的最简单方式是,对数据的操作过程进行加锁,保证同一时刻只有一个线程对该数据进行操作,保证对数据操作的原子性。

//创建一把锁,对数据操作之前进行加锁,操作完后再解锁
mutex m;
void fun(size_t num)
{
	for (size_t i = 0; i < num; ++i)
	{
		m.lock();
		sum++;
		m.unlock();
	}
}

当一个线程进行了加锁,这时其他线程就只能阻塞,影响程序的运行效率,但如果控制不好,如当线程在解锁之前就异常退出,就可能会造成死锁,所以加锁操作也非万全。

故,C++11中引入了原子操作。
所谓原子操作:即不可被中断的一个或一系列操作,C++11引入的原子操作类型,使得线程间数据的同步变得非常高效。
在C++11中,不需要对原子类型变量进行加锁解锁操作,线程能够对原子类型变量互斥的访问。
在这里插入图片描述

//原子操作变量头文件
#include<atomic>

//定义原子操作变量sum,其操作过程都是原子操作
atomic_long sum{ 0 };

void fun(size_t num)
{
	for (size_t i = 0; i < num; ++i)
	{
		sum++; //自加过程为原子操作
	}
}

在这里插入图片描述
另外,用户也可以使用atomic类模板,定义自己需要的任意的原子类型。

//声明一个类型为T的原子类型变量t
atomic<T> t;

注意:原子类型通常属于"资源型"数据,多个线程只能访问单个原子类型的拷贝,因此在C++11中,原子类型只能从其模板参数中进行构造,不允许原子类型进行拷贝构造、移动构造以及operator=等,标准库已将atmoic模板类中的拷贝构造、移动构造、赋值运算符重载默认删除掉了。

六、多线程安全问题

1. 引入

多线程环境下,原子操作类型变量保证该变量的安全性,高效又不易出现死锁的问题。
在有些时候,得确保某段代码的安全性,这时候得借助锁来进行相应的控制。但是锁控制不好就会造成死锁问题。
针对问题,C++11中采用RAII思想对锁进行了封装(lock_guard 和 unique_lock)。

2. C++11的mutex

Mutex种类说明
std::mutex类的对象之间不可拷贝和移动
std::recursive_mutex递归操作中,允许同一个线程对互斥量多次上锁,释放互斥量时需要调用与该锁层次深度相同次数的unlock()
std::timed_mutex有控制时限的锁,接受一个时间范围和一个时间点作为参数,在超过时间范围或时间点后未获得锁则直接返回false
std::recursive_timed_mutex同一线程内可递归的timed_mutex

3. lock_guard

std::lock_guard为C++11中定义的类模板,为避免死锁的问题,采用RAII思想,调用构造函数位置自动进行加锁,调用析构函数位置自动进行解锁。

//lock_guard类模板
	// LOCKS
template<class _Mutex>
	class lock_guard
	{	// class with destructor that unlocks mutex
public:
	typedef _Mutex mutex_type;

	explicit lock_guard(_Mutex& _Mtx)
		: _MyMutex(_Mtx)
		{	// construct and lock
		_MyMutex.lock();
		}

	lock_guard(_Mutex& _Mtx, adopt_lock_t)
		: _MyMutex(_Mtx)
		{	// construct but don't lock
		}

	~lock_guard() _NOEXCEPT
		{	// unlock
		_MyMutex.unlock();
		}

	lock_guard(const lock_guard&) = delete;
	lock_guard& operator=(const lock_guard&) = delete;

private:
	_Mutex& _MyMutex;
	};

可以看到,通过RAII思想可以有效避免死锁问题,但是太单一,用户并不可以对锁进行控制。

4. unique_lock

unique_lock类模板也是采用RAII的方式对锁进行了封装,且以独占所有权的方式管理mutex对象的上锁和解锁操作,即其对象之间不能发生拷贝。在构造(或移动(move)赋值)时,unique_lock 对象需要传递一个 Mutex 对象作为它的参数,新创建的unique_lock 对象负责传入的 Mutex 对象的上锁和解锁操作。
使用以上类型互斥量实例化unique_lock的对象时,自动调用构造函数上锁unique_lock对象销毁时自动调用析构函数解锁,可以很方便的防止死锁问题。

相较于lock_guard,unique_lock在封装上就丰富了很多。

成员函数说明
lock上锁
try_lock尝试锁住互斥量,如果互斥量被其他线程占有,则当前线程也不会被阻塞,直接返回false
try_lock_for接受一个时间范围,等待时间,超时后仍未获得锁并不会阻塞,而是直接false返回
try_lock_until接受一个时间点,等待时间,超时后仍未获得锁并不会阻塞,而是直接false返回
unlock解锁
移动赋值将当前对象所管理的互斥量的所有权转移给另一个unique_lock对象
swap与另一个unique_lock对象互换所管理的互斥量所有权
release返回它所管理的互斥量对象的指针,并释放所有权
owns_lock返回当前对象是否上了锁
operator bool()返回当前对象是否上了锁,同owns_lock()
mutex返回当前unique_lock所管理的互斥量的指针
//unique_lock类模板
template<class _Mutex>
class unique_lock
{	// whizzy class with destructor that unlocks mutex
public:
	typedef unique_lock<_Mutex> _Myt;
	typedef _Mutex mutex_type;

	// CONSTRUCT, ASSIGN, AND DESTROY
	unique_lock() _NOEXCEPT
		: _Pmtx(0), _Owns(false)
	{	// default construct
	}

	explicit unique_lock(_Mutex& _Mtx)
		: _Pmtx(&_Mtx), _Owns(false)
	{	// construct and lock
		_Pmtx->lock();
		_Owns = true;
	}

	unique_lock(_Mutex& _Mtx, adopt_lock_t)
		: _Pmtx(&_Mtx), _Owns(true)
	{	// construct and assume already locked
	}

	unique_lock(_Mutex& _Mtx, defer_lock_t) _NOEXCEPT
		: _Pmtx(&_Mtx), _Owns(false)
	{	// construct but don't lock
	}

	unique_lock(_Mutex& _Mtx, try_to_lock_t)
		: _Pmtx(&_Mtx), _Owns(_Pmtx->try_lock())
	{	// construct and try to lock
	}

	template<class _Rep, class _Period>
	unique_lock(_Mutex& _Mtx,
		const chrono::duration<_Rep, _Period>& _Rel_time)
		: _Pmtx(&_Mtx), _Owns(_Pmtx->try_lock_for(_Rel_time))
	{	// construct and lock with timeout
	}

	template<class _Clock, class _Duration>
	unique_lock(_Mutex& _Mtx,
		const chrono::time_point<_Clock, _Duration>& _Abs_time)
		: _Pmtx(&_Mtx), _Owns(_Pmtx->try_lock_until(_Abs_time))
	{	// construct and lock with timeout
	}

	unique_lock(_Mutex& _Mtx, const xtime *_Abs_time)
		: _Pmtx(&_Mtx), _Owns(false)
	{	// try to lock until _Abs_time
		_Owns = _Pmtx->try_lock_until(_Abs_time);
	}

	unique_lock(unique_lock&& _Other) _NOEXCEPT
		: _Pmtx(_Other._Pmtx), _Owns(_Other._Owns)
	{	// destructive copy
		_Other._Pmtx = 0;
		_Other._Owns = false;
	}

	unique_lock& operator=(unique_lock&& _Other) _NOEXCEPT
	{	// destructive copy
		if (this != &_Other)
		{	// different, move contents
			if (_Owns)
				_Pmtx->unlock();
			_Pmtx = _Other._Pmtx;
			_Owns = _Other._Owns;
			_Other._Pmtx = 0;
			_Other._Owns = false;
		}
		return (*this);
	}

	~unique_lock() _NOEXCEPT
	{	// clean up
		if (_Owns)
		_Pmtx->unlock();
	}

	unique_lock(const unique_lock&) = delete;
	unique_lock& operator=(const unique_lock&) = delete;

	// LOCK AND UNLOCK
	void lock()
	{	// lock the mutex
		_Pmtx->lock();
		_Owns = true;
	}

	bool try_lock() _NOEXCEPT
	{	// try to lock the mutex
		_Owns = _Pmtx->try_lock();
		return (_Owns);
	}

	template<class _Rep,
	class _Period>
		bool try_lock_for(const chrono::duration<_Rep, _Period>& _Rel_time)
	{	// try to lock mutex with timeout
			_Owns = _Pmtx->try_lock_for(_Rel_time);
			return (_Owns);
		}

	template<class _Clock,
	class _Duration>
		bool try_lock_until(
		const chrono::time_point<_Clock, _Duration>& _Abs_time)
	{	// try to lock mutex with timeout
			_Owns = _Pmtx->try_lock_until(_Abs_time);
			return (_Owns);
		}

	bool try_lock_until(const xtime *_Abs_time)
	{	// try to lock the mutex until _Abs_time
		_Owns = _Pmtx->try_lock_until(_Abs_time);
		return (_Owns);
	}

	void unlock()
	{	// unlock the mutex
		_Pmtx->unlock();
		_Owns = false;
	}

	// MUTATE
	void swap(unique_lock& _Other) _NOEXCEPT
	{	// swap with _Other
		_STD swap(_Pmtx, _Other._Pmtx);
		_STD swap(_Owns, _Other._Owns);
	}

	_Mutex *release() _NOEXCEPT
	{	// disconnect
		_Mutex *_Res = _Pmtx;
		_Pmtx = 0;
		_Owns = false;
		return (_Res);
	}

	// OBSERVE
	bool owns_lock() const _NOEXCEPT
	{	// return true if this object owns the lock
		return (_Owns);
	}

	explicit operator bool() const _NOEXCEPT
	{	// return true if this object owns the lock
		return (_Owns);
	}

	_Mutex *mutex() const _NOEXCEPT
	{	// return pointer to managed mutex
		return (_Pmtx);
	}

private:
	_Mutex *_Pmtx;
	bool _Owns;
};

本节完!!!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值