IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++ 标准库之移交线程归属权解析 -> 正文阅读

[C++知识库]C++ 标准库之移交线程归属权解析

前景知识

理解线程归属权的移交方法,需要了解std::move(右值转换)和std::forward(完美转发)相关知识,先介绍三个规则。

规则1(引用折叠规则):如果间接的创建一个引用的引用,则这些引用就会“折叠”。在所有情况下(除了一个例外),引用折叠成一个普通的左值引用类型。一种特殊情况下,引用会折叠成右值引用,即右值引用的右值引用, T&& &&。即

  • X& &、X& &&、X&& &都折叠成X&
  • X&& &&折叠为X&&

规则2(右值引用的特殊类型推断规则):当将一个左值传递给一个参数是右值引用的函数,且此右值引用指向模板类型参数(T&&)时,编译器推断模板参数类型为实参的左值引用,如:

template<typename T> 
void f(T&&);

int i = 42;
f(i)

上述的模板参数类型T将推断为int&类型,而非int。若将规则1和规则2结合起来,则意味着可以传递一个左值int i给f,编译器将推断出T的类型为int&。再根据引用折叠规则 void f(int& &&)将推断为void f(int&),因此,f将被实例化为: void f<int&>(int&)。从上述两个规则可以得出结论:如果一个函数形参是一个指向模板类型的右值引用,则该参数可以被绑定到一个左值上

规则3(static_cast转换):虽然不能隐式的将一个左值转换为右值引用,但是可以通过static_cast显示地将一个左值转换为一个右值。【C++11中为static_cast新增的转换功能】。标准库中move的定义如下:

template<typename T>
typename remove_reference<T>::type && move(T&& t)
{
    return static_cast<typename remove_reference<T>::type &&>(t);
}
  • ?std::move

move函数的参数T&&是一个指向模板类型参数的右值引用【规则2】,通过引用折叠,此参数可以和任何类型的实参匹配(传递左值时,折叠成T&,传递右值时折叠成T&&),因此move既可以传递一个左值,也可以传递一个右值。从move的定义可以看出,move自身除了做一些参数的推断之外,返回右值引用本质上还是靠static_cast<T&&>完成的。注意,std::move返回的永远是右值引用。

  • std::forward

std::forward<T>()不仅可以保持左值或者右值不变,同时还可以保持const、lreference、rreference、validate等属性不变。

移交线程归属权

何为移交线程归属权?假设编写一个函数,功能是创建线程,并置于后台运行,但该函数本身不等待线程完结,而是将其归属权向上移交给函数的调用者;或相反地,读者想创建线程,遂将其归属权传入某个函数,由它负责等待该线程结束。两种操作都需要转移线程的归属权。

std::thread支持移动语义是移交线程归属权的前提,所谓移动语义,简单来说就是资源只能移动不可以拷贝。

std::thread 移交线程归属权实例解析:

void some_function();
void some_other_function();
std::thread t1(some_function);            // -----①
std::thread t2 = std::move(t1);           // -----② 
t1 = std::thread(some_other_function);    // -----③
std::thread t3;                           // -----④
t3 = std::move(t2);                       // -----⑤
t1 = std::move(t3);                       // -----⑥ 该赋值操作会终止整个程序

首先,我们启动新线程①,并使之关联t1。接着,构建t2,在其初始化过程中调用std::move(),将新线程的归属权显式地转移给t2[9]②。在②之前,t1关联着执行线程,some_function()函数在其上运行;及至②处,新线程关联的变换为t2。

然后,启动另一新线程③,它与一个std::thread类型的临时对象关联。新线程的归属权随即转移给t1。这里无须显式调用std::move(),因为新线程本来就由临时变量持有,而源自临时变量的移动操作会自动地隐式进行。

t3按默认方式构造④,换言之,在创建时,它并未关联任何执行线程。在⑤处,t2原本关联的线程的归属权会转移给t3,而t2是具名变量,故需再次显式调用std::move(),先将其转换为右值。经过这些转移,t1与运行some_other_function()的线程关联,t2没有关联线程,而t3与运行some_function()的线程关联。

在最后一次转移中⑥,运行some_function()的线程的归属权转移到t1,该线程最初由t1启动。但在转移之时,t1已经关联运行some_other_function()的线程。因此std::terminate()会被调用,终止整个程序。该调用在std::thread的析构函数中发生,目的是保持一致性。2.1.1节已解释过,在std::thread对象析构前,我们必须明确:是等待线程完成还是要与之分离。不然,便会导致关联的线程终结。赋值操作也有类似的原则:只要std::thread对象正管控着一个线程,就不能简单地向它赋新值,否则该线程会因此被遗弃。

std::thread支持移动操作的意义是,函数可以便捷地向外部转移线程的归属权。

一个更加完整的例子:

#pragma once
#include<thread>

class joining_thread
{
	using this_type = joining_thread;
	using this_thread = std::thread;
	this_thread t;
public:
	joining_thread() noexcept = default;
	//模板函数,func可以是函数指针,类成员函数,函数对象或者lamda表达式 
	//std::forward 完美转发,保留参数的左右值,常量及其引用的属性
	template<typename Callable,typename ... Args>
	explicit joining_thread(Callable && func, Args&& ... args) :
		t(std::forward<Callable>(func), std::forward<Args>(args)...)
	{}

	explicit joining_thread(this_thread t_) noexcept :t(std::move(t_)) {}

	joining_thread (this_type && other) noexcept :t(std::move(other.t)){}

	//析构函数
	~joining_thread() noexcept
	{
		if (joinable())
		{
			join();
		}
	}

	this_type & operator = (this_type && other) noexcept
	{
		if (joinable())
		{
			join();
		}
		t = std::move(other.t);
		return *this;
	}

	this_type & operator = (this_thread other) noexcept
	{
		if (joinable())
		{
			join();
		}
		t = std::move(other);
		return *this;
	}

	bool joinable() const noexcept
	{
		return t.joinable();
	}

	void join()
	{
		t.join();
	}

	void detach()
	{
		t.detach();
	}

	std::thread & as_thread() noexcept
	{
		return t;
	}

	const std::thread & as_thread() const noexcept
	{
		return t;
	}


};

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-03-21 20:30:29  更:2022-03-21 20:33:20 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 3:07:18-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码