c++11
alignas
struct alignas(8) S {} //定义结构体 同时指定分配给结构体的大小为8字节
alignof(与内存对齐相关)
struct obj{char a;int b;}
alignof(obj)=4; //alignof(obj)=4表示对于obj,其内存对齐是以多少字节为单位对齐
对于单个变量char 其alignof(char)=1,单个字节对齐
and_eq => &=
bitand 按位& bitor 按位|
a bitand b; a bitor b;
decltype
decltype(double) y; y为double类型
decltype((double)) z=y;//decltype为double&类型, double& z=y(double);
template<typename T,typename U>
auto add(T a,U b) -> decltype(a+b)//根据传入的参数类型来确定最终返回的类型
{
return a+b;
return (c);//auto c=a+b,返回的是引用
}
explicit明确类型
struct A{
operator int() const {return 0;}//A实例化的对象可以转换为int类型int s=a(弱转)
}
nullPointer(nullptr)
int* a=nullptr;
类的前置声明
在一个类中要使用下面的类,需先在要使用的类前声明被使用的类
同时在该类中,只能使用声明的类的指针传参(因为编译器并不知道声明的类的大小,不知道传值传多少,指针大小与系统有关,大小已知)
同时在头文件中若是并没有使用到另一个头文件的类,只是使用另一头文件的类的指针,则可以只在头文件中声明使用类指针的类,没有必要include 相关头文件
如果在类中使用声明的类,则要include
在一个头文件里加前置类声明还是include头文件
对于A类:要include头文件,因为编译器要知道继承的A类的具体成员
B同理
C类:在Good里成员变量m_cGroup的大小受C具体大小的影响
D类:D是其固定类成员,要知道大小 D m_dObject
E类:可以去掉include "E.h" ,class E;前缀声明即可;对于编译器来说,只需要知道E是作为返回值类型即可,不需知道具体大小。
需include :作为基类、作为类成员
作为函数参数接口、返回值,只需声明即可
类的一些拷贝函数
在一个类中,不希望有拷贝构造函数,同时不希望编译器自动生成相关函数,可以在类内声明拷贝构造函数,但是不去实现它。
在c++11中,对于废弃的函数;不需要的函数,同时不要编译器自动生成,在函数声明上可以:
RuleOfThree(const RuleOfThree& other)=delete;
RuleOfThree& operator=(const RuleOfThree& other)=delete;
左值与右值
对于不能取地址的是右值 如:auto add=&(2+3); error
右值引用:int& e=a;//左值引用 int& le=2;//error int&& re=2;//右值引用
值得一提的是,左值的英文简写为“lvalue”,右值的英文简写为“rvalue”。很多人认为它们分别是"left value"、"right value" 的缩写,其实不然。lvalue 是“loactor value”的缩写,可意为存储在内存中、有明确存储地址(可寻址)的数据,而 rvalue 译为 "read value",指的是那些可以提供数据值的数据(不一定可以寻址,例如存储于寄存器中的数据)。
- 左值可以当作右值
- 有名称的、可以获取到存储地址的表达式即为左值;反之则是右值。
- &左值引用,只能引用左值,无法对右值添加引用 ;常量引用可以引用右值 const int& c=10;
- 和声明左值引用一样,右值引用也必须立即进行初始化操作,且只能使用右值进行初始化
- 右值引用可以对右值进行修改 int&& a=10; a=100;
- 将左值std::move()为右值,其地址是一样的
int main()
{
std::vector<int> a;//左值
for(int i=0;i<10;i++) a.push_back(i);
auto b=a;//b也是左值
auto& c=b;//c左值引用
auto&& d=std::move(b);//std::move(b)将左值b转成右值,返回右值vector<int>&&
//
}
std::move(b)返回右值,调用拷贝构造函数 const RuleOfFive&常量引用可以接收右值
将b的右值(里的数据)转为左值,之后不在使用b
右值拷贝移动到左值后,不再使用右值,不确定右值
对于e=std::move(e);相同的对象需考虑
print()函数里if(m_value)注释掉 改为assert(m_value);
一些类内变量的初始化(对于常量成员变量和引用成员常量 必须用初始化成员列表方式)
初始化列表中成员变量的初始化与列表的顺序无关,与类内成员变量的声明顺序相关(考虑垃圾值)如:
int x;
int y;
X(int m):y(m),x(y)//先初始化x,y还未知道具体,y是垃圾值,x是垃圾值
{}
#include <iostream>
using namespace std;
class BClass
{
public://此处i为一般的常量,ci为常量变量,ri为引用成员变量,仅i可以再构造函数内初始化
BClass() : i(1), ci(2), ri(i) // 对于常量型成员变量和引用型成员变量,必须通过
{ // 参数化列表的方式进行初始化。在构造函数体内进
} // 行赋值的方式,是行不通的。
private:
int i; // 普通成员变量
const int ci; // 常量成员变量
int &ri; // 引用成员变量
static int si; // 静态成员变量
//static int si2 = 100; // error: 只有静态常量成员变量,才可以这样初始化
static const int csi; // 静态常量成员变量
static const int csi2 = 100; // 静态常量成员变量的初始化(Integral type) (1)
static const double csd; // 静态常量成员变量(non-Integral type)
//static const double csd2 = 99.9; // error: 只有静态常量整型数据成员才可以在类中初始化
};
// 静态成员变量的初始化(Integral type)
int BClass::si = 0;//不能写成int static BClass::si=0;即不能出现static
// 静态常量成员变量的初始化(Integral type)
const int BClass::csi = 1;//此处也一样,要么在定义时初始化,要么在此处初始化,但不能加static
// 静态常量成员变量的初始化(non-Integral type)
const double BClass::csd = 99.9;//同上
int main(void)
{
BClass b_class;
b_class.print_values();
getchar();
return 0;
}
类的析构函数(不要在析构函数里抛出异常)
类的析构函数默认不抛出异常:~B() noexcept{}
改为允许抛出异常 ~B() noexcept(false) {}
try{}catch(std::string const& e){}//假设抛出的异常为string类型
class A;
class B :public A
{
public:
B(){}
inline ~B() noexcept
//在析构函数里会调用成员的析构函数和基类的析构函数,在基类里有调用其基类析构函数,可能会很长
{
}
}
inline内联函数
内联函数一般都是1-5行的小函数, 谨慎对待析构函数, 析构函数往往比其表面看起来要更长, 因为有隐含的成员和基类析构函数被调用;
一般要是想阻止编译器生成析构函数 ~Class(); 声明
类的构造函数
类的构造函数失败应该抛出异常
group() :m_string(new char[10]) {
throw std::string("error");
}
虚函数
在基类的析构函数,要加virtual
基类指针=派生类new 通过delete 基类指针,即销毁真正new出来的派生类内存
将一个类指针转为另一个类指针(把基类指针转为派生类指针)
:group* g=static_cast<group*> base;
:group* g=dynamic_casty<group*> base;
(1)析构函数定义为虚函数时:基类指针可以指向派生类的对象(多态性),如果删除该指针delete []p;就会调用该指针指向的派生类析构函数,而派生类的析构函数又自动调用基类的析构函数,这样整个派生类的对象完全被释放。
(2)析构函数不定义为虚函数时:编译器实施静态绑定,在删除基类指针时,只会调用基类的析构函数而不调用派生类析构函数,这样就会造成派生类对象析构不完全。
在一个基类的构造函数和析构函数中调用其虚函数,实际调用其本身的虚函数,不会调用派生类重写的函数
虚函数特性不存在
一般一个类内没有virtual虚函数,一般不用把析构函数设为虚析构函数
new
new除了分配内存外,还调用相关构造函数对内存数据初始化
智能指针
std::shared_ptr<int> p(new int(10));
共享指针p指向new出来的10 整型
其基类是ObjectPtr p 函数参数(const ObjectPtr& obj)
各个类里面都有指针指向另一个类
weak_ptr 检查资源是否存在
weak_ptr不使用监控的资源,要想使用资源,需转为shared_ptr 通过weak_ptr.lock()得到shared_ptr访问资源
auto resource =wp.lock();如果指向的资源已被释放,返回nullptr
use_count()返回资源的管理者,不包含监听的weak_ptr
通过shared_ptr直接初始化,也可以通过隐式转换来构造;
允许移动构造,也允许拷贝构造。
weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。
w.user_count():返回weak_ptr的强引用计数;
w.reset(…):重置weak_ptr。释放被管理对象的所有权。
shared_fom_this()
在一个类中,没有定义初始化类本身的智能指针,但在类的函数里又需要传入本类的智能指针
把类本身作为智能指针传参
在类定义时:
class child : public std::enable_shared_from_this<child>
{}
在需要传参的函数参数里void function(shared_from_this(),...);
unique_ptr
std::move(unique_ptr)转为右值 原unique_ptr不在管理资源,
被移动资源的“老”智能指针内的成员指针已经被置空,再次使用的话会访问到一个nullptr
将unique_ptr转为shared_ptr : shared_ptr ptr(std::move(unique_ptr)); 原unique_ptr不在管理资源
lambda
inline函数可作为对象变量,形参
[]存储lambda表达式要捕获的值,()内的参数为形参,可供外部调用传值。lambda表达式可以直接调用
值的方式来捕获的。所以无法在lambda表达式内部修改age和name的值
以引用方式捕获的。可以在lambda表达式中修改局部变量的值。
C++的lambda表达式虽然可以捕获局部变量的引用,达到类似闭包的效果,但不是真的闭包,局部变量仍旧有其局部生命周期
原函数的几个参数通过bind绑定传值,返回一个新的可调用对象
//绑定全局函数
auto newfun1 = bind(globalFun2, placeholders::_1, placeholders::_2, 98, "worker");
//相当于调用globalFun2("Lily",22, 98,"worker");
newfun1("Lily", 22);
可以用function存储形参和返回值相同的一类函数指针,可调用对象,lambda表达式等
auto fname = [](string name)
{
cout << "this is auto " << endl;
cout << "hello " << name << endl;
};
fname("Rolin");
typedef void (*P_NameFunc)(string name);
// 3 函数指针
P_NameFunc fname2 = [](string name)
{
cout << "this is P_NameFunc " << endl;
cout << "hello " << name << endl;
};
fname2("Vivo");
-----------------------
也可以通过function对象接受lambda表达式
function<void(string)> funcName;
funcName = [](string name)
{
cout << "this is function " << endl;
cout << "hello " << name << endl;
};
funcName("Uncle Wang");
外面的变量,用到就以引用的方式使用,若是=表示:外部的变量要是用到就用拷贝的方式拷贝使用
也可以auto lacal=[a,b,c] () { }
一些STL
array
对原生数组的封装: std::array<int,10> a={1};{1}初始化为1
内存分配在栈
对于swap函数,array交换每一个元素,array里的元素较多时,效率较低
访问:array.at(i) array[i] array.front(); array.back();
auto info=std::get<1>(array);读array下标为1的元素
std::array<char,5> arr={'a','b','c','d','e'};
std::cout<<std::get<1>(arr)<<std::endl; //==>b
vector的swap只交换指针
vector.insert(vector.end(),10);//在尾部插入一个10
vector.insert(vector.end(),10,5.4f);//在尾部插入10个5.4f emplace(b.end(),10);//类似
deque
可以头尾访问元素(双端队列)
list
list.remove(1.0f);//删除所有符合的值
list.remove_if([](auto v) {return v>100.0f;});//删除v里所有大于100.0f的值
set
set不允许重复,元素自动排序,O (logn)
std::set<(float)> s;
set<person,compareAge> s;//按照仿函数排序
对于set查找,可以使用STL提供的find() :iter=std::find(set.begin(),set.end(),aa);//aa为查找的元素,查找是按照类重载的==完全相同才符合的方式。
对于set本身的find(),:set.find(aa);//find()是根据set提供的仿函数查找的,只要有相关项即符合
multiset
可重复
map
std::map<const key,value> m; 能根据仿函数进行相关操作,不能存重复值
map 返回值是pair<const int,std::string>&
map可以使用insert插入 map.insert(std::make_pair(100,"pair"));
map里存的是pair
map中
auto& findinfo=map.at[10];//查找key值为10的,有就返回value,没有就抛出异常
需要在try{}catch(){}里用
C++ 11 标准中,还为 map 容器增添了移动构造函数。当有临时的 map 对象作为参数,传递给要初始化的 map 容器时,此时就会调用移动构造函数。举个例子:
#创建一个会返回临时 map 对象的函数
std::map<std::string,int> disMap() {
std::map<std::string, int>tempMap{ {"C语言教程",10},{"STL教程",20} };
return tempMap;
}
//调用 map 类模板的移动构造函数创建 newMap 容器
std::map<std::string, int>newMap(disMap());
unordered_map
unordered_map使用要对第一项hash
针对Position单独特化
hash:
多线程原子atomic
# include<atomic>
std::atomic<int> m_count;//对要进行原子操作的变量包装
mutex
在临界资源里定义互斥锁
std::mutex m_mutex;//作为变量
m_mutex.lock();//对临界资源加锁
m_mutex.unlock();//对临界资源解锁
STL lock死锁问题:
std::lock(a.mutex,b.mutex,...);//要锁住对象的锁
std::lock_guard<std::mutex> locka(a.mutex,std::adopt_lock);//只负责释放a.mutex锁
vector<> 容器的 emplace_back()向容器尾部插入数据
在操作容器时可以调用对应类型的构造数,在插入对象时会调用相关构造函数,向容器中插入对象数据时,可以传入其构造函数数据,在插入到容器时创建对象(隐式创建)
多线程
t.join();//主线程阻塞,等待子线程结束
t.detach();//主线程不阻塞等待子线程,主线程退出后的子线程由运行时库管理;使用detach不能在使用join
t.joinable();//判断是否能join或detach
std::this_thread::get_id()获取线程id
使用类创建多线程
#include<iostream>
#include<unistd.h>
#include<thread>
class Thread{
public:
void operator()(int num)
{
std::cout<<"类线程"<<std::endl;
}
};
int main()
{
Thread a;//对象
std::thread t(a,10);//使用对象开启线程,对象拷贝构造函数到子线程
if(t.joinable()) t.join();
std::cout<<"主线程"<<std::endl;
}
使用detach结束,若是子线程使用了主线程的资源,会出现主线程结束,子线程仍在使用主线程资源
传递临时对象作为线程参数
thread t(function,agrc,argc);//函数带参
使用detach():
主线程中的传值,字符串数组地址
子线程中以(const int& i,char str[])方式接收;在子线程中i并不是主线程的变量,而是复制主线程的变量值
应此可以在传值时,子线程不以引用接收,const int i
对于主线程字符串数组的接收,使用const string& str(将字符串数组转为string)(考虑何时将字符串数组转为string
在主线程创建子线程时:thread t(func,i,string(str)));通过创建string临时对象,让子线程接收临时对象
主线程拷贝一份字符串
//在创建线程时,构造临时对象传递给子线程,(会调用有参构造,后调用拷贝构造)
传基本类型变量:使用值传递
传类,地址等:使用临时对象,用const & 接
若是使用隐式类型转换让子线程接收,则是在子线程中进行对象的创建
若是使用临时对象的模式让子线程接收,则是在主线程中进行对象的创建和拷贝
由于使用的是拷贝的对象,在子线程中修改的变量不会影响原变量(即使使用join,也是调用拷贝构造);
std::ref()若是期望子线程能修改主线程传入的变量,不发生拷贝构造,则使用std::ref()
(func,std::ref(obj));类对象
子线程不用使用const接收,可修改(仍旧传引用)
传智能指针:传的是unique_ptr<int> p(new int(100));的唯一指针
唯一指针,使用移动语义转化std::move(p)传参 要使用join()
接收unique_ptr<int> pan
使用任意类成员函数创建线程(成员函数指针)
class A
{
void thread_work(int num){}//任意一个线程执行成员函数
};
int main()
{
A a;
std::thread myt(&A::thread_work,a,15);//a可以用(std::ref()= =&a)减少一次拷贝构造
myt.join();
}
线程创建(类的函数地址,类的具体对象,使用该函数的参数)
多线程创建
#include<iostream>
using namespace std;
#include<vector>
#include<thread>
#include<algorithm>
void func(int a)
{
cout << "线程执行" << a << endl;
}
int main()
{
vector<thread> v;
for (int i = 0; i < 10; i++)
{
v.emplace_back(func,i);//创建线程对象,执行函数为func
}
for (auto iter = v.begin(); iter != v.end(); ++iter)
{
iter->join();
}
cout << "主线程" << endl;
return 0;
}
多线程对共享数据的访问
1.共享数据,写者与读者问题:在class中包装,把共享资源放到private里,把类成员函数作为线程执行的入口函数
有的成员函数作为读共享资源的线程函数
有的成员函数作为写共享资源的线程函数
使用类实例化对象,有一个共享资源,用类成员函数开启线程,使用同一个对象,资源共享
互斥量(mutex)保护共享资源
mutex 类型
#include<mutex>
//同读者写者问题:在共享资源里定义互斥锁
std::mutex m_mutex;
m_mutex.lock();//对临界资源加锁
m_mutex.unlock();//对临界资源解锁
lock_guard避免死锁 lock_guard类模板
对临界资源加锁后,可使用
std::lock_guard<std::mutex> locka(a.mutex,std::adopt_lock);//只负责加锁与解锁锁,避免忘记释放锁造成死锁
lock_guard取代了lock(),unlock() ,使用lock_guard后不能再使用lock()和unlock();在构造函数里lock(),析构函数里unlock()
在共享的资源class里
对资源使用前:
std::lock_guard<std::mutex> m_lock_guard(m_mutex);//加互斥变量m_mutex锁
即可
在其定义的作用域结束后释放
可以使用lock_guard避免死锁
死锁问题
std::lock(a.mutex,b.mutex,...);//要锁住对象的锁
std::lock_guard<std::mutex> locka(a.mutex,std::adopt_lock);//只负责释放a.mutex锁
要把两个及以上的锁锁上:
使用std::lock(a.mutex,b.mutex);锁上两个,a.unlock(),b.unlock();释放两个锁
std::lock(a.mutex,b.mutex,...);//要锁住对象的锁,紧接着
std::lock_guard<std::mutex> locka(m_mutex1,std::adopt_lock);//只负责释放a.mutex锁
std::lock_guard<std::mutex> lockb(m_mutex2,std::adopt_lock);
由lock_guard负责释放锁 std::adopt_lock是结构体对象,起标记作用:表示mutex已经 lock了,不需再lock_guard里再lock()
unique_lock
-
类模板(加锁后可以实时解锁)
-
可以使用unique_lock替代lock_guard,第二个参数也可以使用std::adopt_lock,相同含义
-
std::try_to_lock尝试去获取mutex锁,在尝试获取该锁前,不能对mutex加锁;不然可能会加锁两次
std::unique_lock<std::mutex> m_lock(m_mutex,std::try_to_lock); if(m_lock.owns_lock())//拿到了锁 else//没拿到
-
std::defer_lock 前提不能先lock; 初始化一个没有加锁的mutex
可以使用unique_lock的成员函数: 示例: std::unique_lock<std::mutex> sb(m_mutex,std::defer_lock);//初始化没有加锁的mutex sb.lock();//手动加锁,不用解锁 sb.unlock();//解锁,lock会自动加锁,unlock增加灵活性(只有lock才能unlock) sb.try_lock();//尝试加锁,加锁成功返回true,失败返回false sb.release();//返回所管理的mutex对象指针,并释放所有权;unique_lock和m_mutex不再有关系 mutex对象指针:std::mutex* ptr=sb.release();
unique_lock与mutex绑定,其拥有mutex的所有权,可以转移mutex的所有权(不能复制)
std::unique_lock<std::mutex> sb2(std::move(sb));//移动语义(右值引用)sb指向空
返回局部的std::unique_lock<std::mutex>
std::unique_lock<std::mutex> temp_unique_lock()
{
std::unique_lock<std::mutex> temp(m_mutex);
return temp;//存在寄存器中,右值;以右值引用的方式将其转为左值
//unique_lock的移动构造函数
}
std::unique_lock<std::mutex> sbb=temp_unique_lock();
std::call_once(),保证一个函数只被调用一次
- 第二个参数是要保证的函数名
- 需要与一个标记结合使用,(std::once_flag)( 一个结构)
- call_once通过这个标记决定对应的函数是否执行,调用call_once成功后,call_once就把该标记设为“已调用”;后续再调用call_once(),只要once_flag为“已调用”,对应的函数不再执行
std::once_flag m_flag;//全局
std::call_once(m_flag,function_name);//调用,function_name只能执行一次
条件变量 同步信号 std::condition_variable wait() notify_one()
与互斥量配合
在资源里声明该条件变量
std::mutex m_mutex;
std::condition_variable m_cond;//条件变量
/*m_mutex与m_lock绑定了,wait需绑定m_lock
如果lambda表达式的返回值是true,wait()不阻塞,继续执行下面的代码
若是返回false,wait()将解锁互斥量,并阻塞到本行
如果没有第二个lambda参数,wait解锁互斥量,直接阻塞到本行
阻塞后要等到其他线程notify_one解放
*/
std::unique_lock<std::mutex> m_lock(m_mutex);
m_cond.wait(m_lock,[this](){
//等待队列(条件变量)作为调用主体,在某线程中使用该方法,线程(符合条件)就阻塞到该等待队列,配合互斥锁
if(?) return true;
return false;
})
//唤醒:唤醒某个等待队列cond,阻塞到其队列的线程被唤醒:
std::unique_lock<std::mutex> m_lock(m_mutex);
m_cond.notify_one();//紧接着把锁释放;
当wait()被唤醒后,其又不断尝试获取互斥锁
若是wait()有第二个lambda参数,若是为false;其又释放互斥锁,并阻塞
wait()成功返回,继续下面的代码,此时互斥锁是被锁着的
notify_one()通知一个线程,notify_all()通知所有wait()在该等待队列的线程
std::async std::future创建后台任务并返回值
std::async 是一个函数模板,用来启动一个异步任务,启动一个异步任务后,返回一个std::future对象,std::future是一个类模板
启动一个异步任务:创建一个线程并开始执行对应的线程入口函数,返回一个std::future对象,这个std::future对象里边含有线程入口函数所返回的结果(线程返回结果);通过调用future对象的成员函数get()获取线程返回结果 get()只能调用一次
#include<future>
int thread_func()//线程执行入口函数,返回(int)10
{
return 10;
}
int main()
{
std::future<int> p=std::async(thread_func);//启动一个线程,执行函数thread_func,返回结果存到future里<int>与返回结果类型一致
p.get();//使用该对象的get()获取结果;线程执行结束后才能调用get();get()不拿到值会阻塞
}
p.wait();只等待线程返回,不返回结果 wait()、get()主线程会阻塞
若是类成员函数做入口函数:
class A{
int thread_fun(int num);
}
int main()
{
A a;
int temp=19;
std::future<int> p=std::async(&A::thread_fun,&a,temp);
}
向std::async()传递参数std::launch(枚举类型),达到一些目的
std::launch::deferred:线程入口函数被延迟到只有在调用future的wait() get()时才调用
不使用wait() get()不会调用线程入口函数(即使后面调用wait()或get(),也没创建新线程,线程函数在主线程中运行)
std::future<int> p=std::async(std::launch::deferred,thread_func);
std::launch::async,在调用async时就开始立即创建线程
async 默认的参数是(std::launch::async | std::launch::deferred) 在创建时可能调用前者,也可能调用后者(系统决定是异步(创建新线程)还是同步)
std::packaged_task(打包任务)
-
类模板,模板参数是各种可调用对象(线程入口函数);通过std::packaged_task把各种可调用对象包装起来
int thread_fun(int num) {} int main() { std::packaged_task<int(int)> p(thread_fun);//<int(int)>是线程入口函数的返回值类型,传参类型;(thread_fun)是入口函数名 std::thread t1(std::ref(p),1);//将打包的对象传给线程启动的构造函数,1表示线程启动函数thread_fun要传的形参 t1.join(); std::future<int> result=p.get_future();//future里包含packsged里线程执行结果 result.get(); }
-
std::packaged_task<int(int)> p([](int num){ //lambda表达式 });
-
std::packaged_task<int(int)> p(thread_fun); packaged_task可以直接调用 p(10);// 10为函数参数 std::future<int> res=p.get_future(); <<res.get();// 线程返回值
-
通过std::packaged_task把各种可调用对象(可创建线程的函数)包装起来
vector<std::packaged_task<int(int)> > vec;//把包装的包放到容器里
vec.push_back(std::move(p));//p是前面的对象
vec.emplace_back(thread_func);//或
//取出来也用移动语义:
auto iter=vec.begin();
std::packaged_task<int(int)> k=std::move(*iter);//可以删除第一个元素,迭代器失效了
std::promise 类模板(获取另一个线程保存的值)
-
在一个线程中给promise对象赋值,可以在其他线程中取出该值
void thread_func(std::promise<int>& temp,int cal) { //运算 temp.set_value(cal);//把运算的结果存到传进来的promise对象里保存 return; } int main() { std::promise<int> p; std::thread t1(thread_func,std::ref(p),18); t1.join(); //获取t1线程保存的值 std::future<int> ful=p.get_future();//promise与future绑定 用于获取线程保存的值 auto item=ful.get(); }
-
也可以把future
ful传给线程2获取使用线程1的值std::ref(ful) void thread_func2(std::future<int>& temp){} std::thread t2(thread_func2,std::ref(ful));
std::future的其他成员函数
get(),wait()阻塞等待
- 枚举类型
std::future<int> p=std::async(thread_func);//启动一个线程,执行函数
//其他成员函数
std::future_status status=p.wait_for(std::chrono::second(1));//等待线程执行1秒,若是线程执行1秒后还不返回结果,则超时
if(status==std::future_status::timeout)//超时
{
//...
}
else if(status==std::future_status::ready)//没超时
{}else if(status==std::future_status::deferred)//线程延迟执行
//线程延迟执行:async(std::launch::deferred,thread_func);//子线程在主线程执行
std::shared_future
-
在 future里,只能使用一次get()获取线程值,因为get()是移动语义,会将原future对象值移动到要存的临时变量上;原future对象是空,再次使用出错
-
若是有多个线程要通过get()获取future里的值;使用std::shared_future
-
std::shared_future,是类模板;主要使用是把原future对象传给shared_future
std::packaged_task<int(int)> p(thread_fun); packaged_task可以直接调用 p(10);// 10为函数参数 std::future<int> res=p.get_future(); std::shared_future<int> newres(std::move(res));//即可get()多次
std::atomic原子操作 类模板
对于一段代码,可以使用互斥量来达到排队访问
对于一个变量,可以使用原子操作,原子操作比互斥量效率更高
std::atomic<int> m_count=0;//变量m_count是原子
对其操作按常规操作即可
一般的原子操作针对于++、--、+=、-= 、&=等基本操作
使用a=a+1就不支持原子操作
std::async继续
async 默认的参数是(std::launch::async | std::launch::deferred) 在创建时可能调用前者,也可能调用后者(系统决定是异步(创建新线程)还是同步)
系统决定是异步(创建新线程)还是同步:
-
std::thread创建线程,如果系统资源紧张,创建失败,程序崩溃
-
如果用std::async创建异步任务,一般不会崩溃;若系统资源紧张无法创建新线程,std::async这种不加额外参数的调用,就不会创建新线程;后续谁调用了get()函数,请求结果,这个(异)同步任务就运行在执行这条get()语句所在的线程上。
-
如果使用std::launch::async创建新线程,系统资源紧张就会崩溃。
-
使用默认的async,判断系统到时候是创建异步任务还是同步任务:使用wait_for()判断
future接收async(fun): std::future_status s=futu.wait_for(std::chrono::seconds(6)); if(s==std::future_status::deferred)//等判断是选择何种创建方式
递归加锁 递归的独占互斥量recursive_mutex
mutex 多次lock会出错(独占互斥量)
允许同一个线程,同一个互斥量多次被锁lock()
std::recursive_mutex m_mutex;//递归的独占互斥量
std::lock_guard<std::recursive_mutex> sb(m_mutex);//可多次加锁
带超时的互斥量
-
std::timed_mutex 函数1:try_lock_for(..):参数是一段时间,在等待的一段时间里,如果拿到锁,就继续走,如果在这段时间里拿不到锁,同样不阻塞,执行后面的代码 函数2:try_lock_until():参数是一个未来的时间点,在这个未来时间点还未到来之前,如果拿到了锁,就执行里面的代码;若是时间点到了还未拿到锁,也不阻塞,继续执行下面的代码
std::timed_mutex m_mutes; std::chrono::milliseconds timeout(100); if(m_mutex.try_lock_for(timeout))//在这时间段内尝试获得锁,拿到则返回true //if(m_mutex.try_lock_until(chrono::steady_clock::now()+timeout))//未来时间点到来前尝试获得锁 { //拿到了锁 }else{ //时间段内没拿到锁,继续执行 }
-
std::recursive_timed_mutex 带超时的递归的独占互斥量;基本同std::timed_mutex一样
虚假唤醒
一个wait()可能被多次使用notify_one()唤醒,可能在wait()被唤醒后临界区没有数据使用
虚假唤醒:wait()第二个参数使用lambda通过判断是否位空,返回true false
std::atomic以原子的方式读写
用一个原子类初始化另一个原子类 不允许使用拷贝构造的方式初始化
应使用:
atomic<int> atm2(atm1.load());//读atm1的值,初始化atm2
atm2.store(12);//以原子的方式将12写入atm2
c++ 基类中若是有纯虚函数,基类中也应有纯虚析构函数
若是基类中没有纯虚析构函数,基类若是以共享指针指向派生类对象,最后基类的析构函数会调用派生类的析构函数
删除vector里特定的所有值
vector.erase(std::remove(vector.begin(),vector.end(),value),vector.end());
remove把所有的value放到vector后面,并且返回所有符合的第一个迭代器