tr。
unique_ptr
的对应关系是一对一,无论哪个时刻,只能有一个管理者拥有指针,也就只能由它负责释放了。假如想转移这种对应关系,只能通过std::move
操作,不过这个操作之后,原先对象的指针就失效了,它也不再负责管理,所有的任务移交给了新的对象。这种特性特别适合资源敏感型的应用。
线程库
除了内存,线程是开发中另一个重要的课题。线程的难点在于不仅要管理线程对象,还要管理线程对象管理的资源,并且保证线程间数据同步。当然标准库已经做得足够好了,我们需要理解的是使用场景的问题。线程库主要包括线程对象thread
,条件对象condition_variable
,锁对象mutex
。
使用thread
可以很方便地把程序写成多线程,只需要三步:
void plus(int a,int b){ //第一步:定义线程中要运行的函数
std::cout<<"running at sub thread"<<std::endl;
std::cout<<"a + b = "<<a+b<<std::endl;
}
int main(){
std::thread thread{plus,1,1}; //第二步,定义std::thread对象,将函数作为参数
std::cout<<"continue running at main thread"<<std::endl;
thread.join(); //第三步调用线程对象的join函数或者detach函数
std::cout<<"sub thread finished!"<<std::endl;
}
//输出
// continue running at main thread
// running at sub thread
// a + b = 2
// sub thread finished!
难点在线程间通信,也就是解决两个问题
- 线程1更新了变量v的值
- 线程2马上能读取到正确的变量v的值,即线程1更新的那个最新值
为了协调这两个过程,就出现了锁对象mutex
和条件对象condition_variable
。锁对象mutex
保证变量按照正确的顺序更改。条件对象condition_variable
保证更改能被其他线程监听到。
int a,b;
bool ready = false;
std::mutex mux;
std::condition_variable con;
void plus() {
std::cout << "running at sub thread" << std::endl;
//因为我们要读取ready的最新值,所以要用锁保证读取结果的有效性
std::unique_lock<std::mutex> guard{ mux };
if (!ready) {
//数据没准备好,休息一下!
con.wait(guard);
}
//这里就可以正确读变量a,b了
std::cout << "a + b =" << a + b << std::endl;
}
int main() {
std::thread thread{ plus};
std::cout << "continue running at main thread" << std::endl;
std::cout << "input a = ";
std::cin >> a;
std::cout << "input b = ";
std::cin >> b;
{
//数据准备好了,该通知子线程干活了,用大括号是因为想让锁因为guard的销毁即使释放,从未保证plus里面能重新获得锁
std::unique_lock<std::mutex> guard{ mux };
//更新数据
ready = true;
//通知
con.notify_all();
}
thread.join();
std::cout << "sub thread finished!" << std::endl;
}
多线程另一个需要注意的问题就是死锁。死锁的前提是有两个锁
- 线程1得到了锁a,还想得锁b
- 线程2得到了锁b,还想得锁a
然后,再加上一个前提:某一时刻,只有一个线程能拥有某个锁,就不难得出以下结论:线程a,b除非某一个放弃已得的锁,不然两个线程都会因为没得到需要的锁而一直死等,形成死锁。同时解决死锁的思路也呼之欲出:既然一个得了a,一个得了b,而锁同一时间只能被一个线程得到,那么所有线程都按先得a,再得b的顺序来就不会有锁被占用的问题了。另一个思路则可以从放弃上入手,既然都得不到,那么接下来的任务也做不了,不如直接放弃已经得到的,所以可以考虑使用timed_mutex
。
其他
还有很多常用的库,如字符串string
,时间chrono
,还有在定义函数变量时常用的functional
,异常exception
,更多的内容可以在cplusplus找的参考。
总结
总的来说,标准库提供了一个展现C++语言能力的平台:帮助开发者更好更快完成开发任务的同时,还能启迪开发者实现更好的抽象和实践。如我就从标准库中学到了更规范地定义函数参数,更好的封装,以及其他好的思路。学习标准库不仅更好地掌握了语言本身,还掌握了更全面地分析问题,解决问题的方法,是值得花费一段时间学习的。
容器类是几乎所有项目都会用到的,也是比较好掌握的,主要可以从数据结构方面对照学习;智能指针则是处理指针问题的好帮手;线程相关的库是比较难掌握的,关键是要想明白使用场景和极端情况下的边界问题。很多时候边界问题可能不那么直观。如线程要求获得锁的情况就分为:锁空闲,锁被其他线程占有,锁被自己占有。不同的边界对于不同的锁,预期结果也是不同的,只有在明确场景的情况下,才能更好地理清锁的关系,从而解决好问题。
最好的学习还是在实践中主动使用。对于我,通常在遇到新问题的时候会先查查标准库有没有相应的库,有的话就是学习这个库的好时机。可以先概览库的定义和解决的问题,然后分析它提供的类,函数,对象等,再将自己的理解转换为项目中的代码,最后在实际效果中检验和修正想法,完成库的学习。