导航

c++一些零碎记录(个人用)

Posted on 2023-06-16 19:57  koodu  阅读(13)  评论(0编辑  收藏  举报

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",指的是那些可以提供数据值的数据(不一定可以寻址,例如存储于寄存器中的数据)。
  1. 左值可以当作右值
  2. 有名称的、可以获取到存储地址的表达式即为左值;反之则是右值。
  3. &左值引用,只能引用左值,无法对右值添加引用 ;常量引用可以引用右值 const int& c=10;
  4. 和声明左值引用一样,右值引用也必须立即进行初始化操作,且只能使用右值进行初始化
  5. 右值引用可以对右值进行修改 int&& a=10; a=100;
  6. 将左值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
  1. 类模板(加锁后可以实时解锁)

  2. 可以使用unique_lock替代lock_guard,第二个参数也可以使用std::adopt_lock,相同含义

  3. 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//没拿到
    
  4. 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(),保证一个函数只被调用一次
  1. 第二个参数是要保证的函数名
  2. 需要与一个标记结合使用,(std::once_flag)( 一个结构)
  3. 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(打包任务)
  1. 类模板,模板参数是各种可调用对象(线程入口函数)通过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();
    }
    
  2. std::packaged_task<int(int)> p([](int num){
    //lambda表达式
    });
    
  3.  std::packaged_task<int(int)> p(thread_fun);
     packaged_task可以直接调用
     
     p(10);// 10为函数参数
     std::future<int> res=p.get_future();
     <<res.get();// 线程返回值
    
  4. 通过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 类模板(获取另一个线程保存的值)
  1. 在一个线程中给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();
    }
    
  2. 也可以把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()阻塞等待

  1. 枚举类型
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
  1. 在 future里,只能使用一次get()获取线程值,因为get()是移动语义,会将原future对象值移动到要存的临时变量上;原future对象是空,再次使用出错

  2. 若是有多个线程要通过get()获取future里的值;使用std::shared_future

  3. 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) 在创建时可能调用前者,也可能调用后者(系统决定是异步(创建新线程)还是同步)

系统决定是异步(创建新线程)还是同步:

  1. std::thread创建线程,如果系统资源紧张,创建失败,程序崩溃

  2. 如果用std::async创建异步任务,一般不会崩溃;若系统资源紧张无法创建新线程,std::async这种不加额外参数的调用,就不会创建新线程;后续谁调用了get()函数,请求结果,这个(异)同步任务就运行在执行这条get()语句所在的线程上。

  3. 如果使用std::launch::async创建新线程,系统资源紧张就会崩溃。

  4. 使用默认的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);//可多次加锁
带超时的互斥量
  1. 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{
    //时间段内没拿到锁,继续执行
    }
    
  2. 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后面,并且返回所有符合的第一个迭代器