移动语义

std::vector<String> v;
v.push_back(“string”);
调用 String::String(const char *);
调用 String::String(const String&);
调用 String::~String()
问题症结在于,临时对象的构造和析构带来了不必要的资源拷贝

如果有一种机制,可以在语法层面识别出临时对象,在使用临时对象构造新对象(拷贝构造)的时候,将临时对象所持有的资源『转移』到新的对象中,就能消除这种不必要的拷贝。

根据其修饰符的不同,可分为非常量左值引用和常量左值引用

int ia = 10;       int &a = ia;
const int ib = 30; int &b = ib;
const int &ri = 20;

非常量左值引用只能绑定到非常量左值
常量左值引用可以绑定到所有类型的值,包括  非常量左值、常量左值、右值(根据语法规则,无法区分出右值)

编译器只对右值引用才能调用转移构造函数和转移赋值函数,而所有命名对象都只能是左值引用,如果已知一个命名对象不再被使用而想对它调用转移构造函数和转移赋值函数,也就是把一个左值引用当做右值引用来使用,怎么做呢?标准库提供了函数 std::move,这个函数以非常简单的方式将左值引用转换为右值引用。

当存在移动语义时,会优先调用

talk  is cheap,Let‘s code

 ///
 /// @file    String.cc
 /// @author  miaobeihai(452686191@qq.com)
 /// @date    2017-05-02 09:45:30
 ///
 
#include <string.h>
#include <iostream>
#include <vector>

using std::cout;
using std::endl;
using std::vector;


class String
{
public:
	String()
	: _pstr(new char[1])
	{
		cout << "String()" << endl;
		_pstr[0] = '\0';
	}

	String(const char * pstr)
	: _pstr(new char[strlen(pstr) + 1])
	{
		cout << "String(const char *)" << endl;
		strcpy(_pstr, pstr);
	}

	//移动构造函数
	String(String && rhs)//右值引用只能绑定到右值
	: _pstr(rhs._pstr) 
	{
		cout << "String(String && rhs)" << endl;
		rhs._pstr = NULL;
	}

	//复制构造函数
	String(const String & rhs) //形参const引用能绑定到左值或者右值上,
							   //意味着它不能区分出传递过来的实参是
							   //左值还是右值;为了达到转移临时对象的内容,
							   //需要新的语法规则能在语法层面识别出右值,
							   //所以C++11中引入了右值引用,右值引用只能绑定到右值
	: _pstr(new char[strlen(rhs._pstr) + 1])
	{
		cout << "String(const String&)" << endl;
		strcpy(_pstr, rhs._pstr);
	}


	//移动赋值运算符 
	String & operator=(String && rhs)
	{
		cout << "String & operator=(String && rhs)" << endl;
		if(this != & rhs)
		{
			delete [] _pstr;
			_pstr = rhs._pstr;

			rhs._pstr = NULL;
		}
		return *this;
	}

	String & operator=(const String & rhs)
	{
		if(this != &rhs)
		{
			delete [] _pstr;

			_pstr = new char[strlen(rhs._pstr) + 1];
			strcpy(_pstr, rhs._pstr);
		}
		return *this;
	}

	~String()
	{
		cout << "~String()" << endl;
		delete [] _pstr;
	}

	friend std::ostream & operator<<(std::ostream & os, const String & rhs);
private:
	char * _pstr;
};

std::ostream & operator<<(std::ostream & os, const String & rhs)
{
	os << rhs._pstr;
	return os;
}

int main(void)
{
	String s1("hello");
	cout << "s1 = " << s1 << endl;

	String s2 = std::move(s1);//move函数显示地将左值引用转换成右值引用
	cout << "s2 = " << s2 << endl;

	cout << "s1 = " << s1 << endl;

	return 0;
}





<think>我们正在讨论C++多线程编程中如何正确使用移动语义。根据用户提供的引用和之前关于Boost.Asio中tcp::socket移动语义的讨论,我们可以将重点扩展到标准库中的std::thread以及其他支持移动语义的类。关键点: 1. std::thread支持移动语义,但不支持复制语义(因为线程资源唯一)。2.移动语义允许我们将线程对象的所有权从一个作用域转移到另一个作用域(例如,将新创建的线程移动到容器中或传递给另一个线程)。 3.在多线程环境中使用移动语义时,需要注意线程对象的生命周期和线程执行顺序。 以下是一个分步指南: ###1.理解std::thread的移动语义-移动构造函数:`thread(thread&&other)noexcept;` -移动赋值运算符:`thread&operator=(thread&&other)noexcept;` -移动后,原线程对象不再代表任何线程(即`joinable()`为`false`)。 ###2.创建线程并移动```cpp#include<iostream> #include<thread> #include<vector> voidthread_func(int id) {std::cout<< "Thread "<< id<< "is running\n";} intmain(){std::vector<std::thread> threads;//创建10个线程,并将它们移动到vector中for (int i=0;i <10; ++i){std::thread t(thread_func,i);threads.push_back(std::move(t)); //必须使用std::move}//等待所有线程完成for (auto&t :threads){if (t.joinable()){t.join();}}return0; }``` ###3.在线程间传递所有权```cpp#include <thread>#include <iostream>void worker() {std::cout<< "Worker thread\n"; }int main() {std::threadt1(worker); //创建线程t1//将t1的所有权转移给t2std::thread t2 =std::move(t1);//此时t1不再拥有线程,t2拥有if (!t1.joinable()) {std::cout<< "t1is notjoinable\n";}t2.join(); //等待t2结束return0; }``` ###4.从函数返回线程```cpp#include<thread>#include <iostream>std::thread create_thread(){return std::thread([]{std::cout<< "Thread createdinsidefunction\n";});} intmain(){std::thread t= create_thread();t.join();return0; }``` ###5.注意事项- **移动后原对象状态**:移动后的线程对象不再关联任何线程(即`joinable()`返回`false`),因此不能对其调用`join()`或`detach()`。- **资源管理**:确保在移动后的线程对象销毁前,已经通过`join()`或`detach()`正确处理了底层线程资源。 -**异常安全**:在移动操作中,由于是noexcept,因此不会抛出异常,但需要注意线程函数的异常可能会在线程内部发生。 ###6.错误示例```cppstd::thread t1(worker); std::threadt2= std::move(t1); t1.join(); //错误!因为t1已经被移动,不可连接```###7.多线程环境下移动语义的应用场景-线程池:将新创建的线程移动到线程池容器中。-任务分发:主线程创建任务线程并移动到工作线程队列。-资源传递:将已创建的线程对象传递给另一个线程进行管理。###8.与Boost.Asio的tcp::socket比较-两者都只支持移动语义,不支持复制。 -移动后原对象都变为无效状态(`tcp::socket`的`is_open()`返回`false`,`std::thread`的`joinable()`返回`false`)。-移动操作都是高效的(仅转移底层资源的句柄)。 ###9.性能优势-移动操作避免了复制资源的开销,仅涉及指针或句柄的复制($O(1)$时间复杂度)[^3]。###10.多线程中移动语义的线程安全性-移动操作本身是原子的(因为只涉及一个指针的复制),但需要确保在移动过程中没有其他线程访问该对象。-通常,移动操作应在一个线程内完成,然后通过同步机制(如互斥锁)将对象的所有权转移到另一个线程。###11.使用RAII管理线程```cppclassThreadRAII {public:enumclass DtorAction{ join, detach };ThreadRAII(std::thread&&t,DtorAction a):action(a), t(std::move(t)) {}~ThreadRAII(){if (t.joinable()){if (action ==DtorAction::join){t.join();} else{t.detach();}}}ThreadRAII(ThreadRAII&&) =default;ThreadRAII& operator=(ThreadRAII&&)= default;std::thread&get(){ returnt;} private:DtorAction action;std::thread t; };int main() {ThreadRAIItr(std::thread(worker), ThreadRAII::DtorAction::join);//当tr离开作用域时,会自动joinreturn0;} ```###总结在多线程编程中使用移动语义:1.使用`std::move`转移线程所有权2.注意移动后原对象的状态(不可连接) 3.利用移动语义高效管理线程生命周期4.结合RAII确保异常安全§§相关问题§§1.如何检测`std::thread`对象是否可连接(joinable)?2.在多线程环境中传递`std::thread`对象时需要注意哪些同步问题?3.除了`std::thread`,C++标准库中还有哪些类支持移动语义?4.如何实现一个线程安全的线程池,使用移动语义来添加新线程?</think>### C++多线程编程中移动语义的正确使用方法 在C++多线程编程中,移动语义是实现高效资源管理的关键技术。以下是正确使用移动语义的要点和实践方法: #### 1. 理解核心原则 - **资源唯一性**:线程对象(`std::thread`)和网络套接字(`tcp::socket`)等资源**不可复制**,只能通过移动转移所有权[^3] - **移动后状态**:移动后原对象进入"空状态"(`joinable() == false`或`is_open() == false`) - **零复制开销**:移动操作仅转移资源句柄,时间复杂度 $O(1)$[^3] #### 2. 线程对象移动语义实践 ```cpp #include <iostream> #include <thread> #include <vector> void worker(int id) { std::cout << "Worker " << id << " started\n"; // 模拟工作 std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::cout << "Worker " << id << " finished\n"; } int main() { std::vector<std::thread> thread_pool; // 创建并移动线程到容器 for(int i = 0; i < 5; ++i) { std::thread t(worker, i); thread_pool.push_back(std::move(t)); // 必须显式移动 } // 等待所有线程完成 for(auto& t : thread_pool) { if(t.joinable()) t.join(); // 检查状态 } } ``` #### 3. 跨线程资源传递 ```cpp #include <thread> #include <mutex> #include <queue> std::mutex mtx; std::queue<std::thread> thread_queue; void resource_owner() { std::thread worker([]{ std::cout << "Processing in worker thread\n"; }); std::lock_guard<std::mutex> lock(mtx); thread_queue.push(std::move(worker)); // 安全转移所有权 } void resource_consumer() { std::thread t; { std::lock_guard<std::mutex> lock(mtx); if(!thread_queue.empty()) { t = std::move(thread_queue.front()); // 获取所有权 thread_queue.pop(); } } if(t.joinable()) t.join(); } ``` #### 4. 函数间传递线程所有权 ```cpp // 工厂函数创建线程 std::thread create_thread() { return std::thread([]{ std::cout << "Thread created in factory\n"; }); // 返回值优化(RVO)或移动语义 } // 接收线程所有权的函数 void manage_thread(std::thread&& t) { if(t.joinable()) { // 执行任务 t.join(); } } int main() { manage_thread(create_thread()); // 链式移动 } ``` #### 5. 异步操作中的生命周期管理 ```cpp #include <future> void async_operation() { auto task = []() -> int { return 42; // 计算结果 }; // 通过移动延长线程生命周期 std::async(std::launch::async, [task = std::move(task)]() mutable { auto result = task(); std::cout << "Result: " << result << std::endl; }); } ``` #### 6. 关键注意事项 1. **状态检查**: ```cpp std::thread t; if(t.joinable()) { /* 安全操作 */ } // 移动前检查 ``` 2. **错误处理**: ```cpp try { std::thread moved = std::move(original); } catch(const std::system_error& e) { std::cerr << "Thread move failed: " << e.what(); } ``` 3. **RAII包装器**(推荐模式): ```cpp class ThreadGuard { public: explicit ThreadGuard(std::thread&& t) : t_(std::move(t)) {} ~ThreadGuard() { if(t_.joinable()) t_.join(); // 自动资源管理 } // 禁用复制 ThreadGuard(const ThreadGuard&) = delete; ThreadGuard& operator=(const ThreadGuard&) = delete; private: std::thread t_; }; ``` #### 7. 性能优化场景 1. **线程池实现**: ```cpp class ThreadPool { public: void add_worker(std::thread&& worker) { workers.push_back(std::move(worker)); } ~ThreadPool() { for(auto& w : workers) if(w.joinable()) w.join(); } private: std::vector<std::thread> workers; }; ``` 2. **任务调度系统**: ```cpp void schedule_task(std::packaged_task<void()>&& task) { std::lock_guard<std::mutex> lock(queue_mutex); // 移动任务到队列 task_queue.push(std::move(task)); } ``` #### 8. 常见错误及避免方法 1. **访问移动后对象**: ```cpp std::thread t1(worker); std::thread t2 = std::move(t1); t1.join(); // 错误!t1已为空状态 ``` 2. **忽略状态检查**: ```cpp std::thread t; t.join(); // 未检查joinable()导致terminate() ``` 3. **未使用std::move**: ```cpp std::vector<std::thread> pool; std::thread t(worker); pool.push_back(t); // 错误!应使用std::move(t) ``` 移动语义在多线程编程中的正确使用可以: - 避免不必要的资源复制 - 实现线程安全的所有权转移 - 简化资源生命周期管理 - 提升并发程序性能[^3]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值