C++11总结

nullptr

空指针由新关键字nullptr表示,类似于Java中的null。

auto

auto声明的变了必须马上初始化,让编译器推断出他的实际类型,并在编译时将auto占位符替换为真正的类型
auto不能用于函数参数
auto不支持非静态成员变量的初始化:int arr[0] = {0}; auto rr[10] = arr;

Raw字符串

Raw是为了解决正则表达式里那些烦人的转义字符\而提供的解决方法
在C++中,如果使用转义字符串来表达,则变成(’(?:[ˆ\\’]|\\.)∗’|"(?:[ˆ\\"]|\\.)∗")|。使用转义字符后,整个字符串变得很难看懂了。
如果使用Raw字符串,改成R"dfp((’(?:[ˆ\’]|\.)∗’|"(?:[ˆ\"]|\.)∗")|)dfp"即可。此处使用的界定字符为"dfp",括号前后加的界定字符会被忽略,而且必须在两边同时出现。

tostring

double f = 1.5;
std::string f_str = std::to_string(f);

  • std::string to_string(int value);
  • std::string to_string(long value);
  • atoi : 将字符串转换为int
  • atol:将字符串转换为long
  • atoll:将字符串转换为long long
  • atof:将字符串转换为float

typedef和using

typedef不能重定义模板 template < typename Val>

  • 声明函数 using func_t = void (*)(int , int)
  • template < typename T> using type_t = T;
  • 在类定时时候using Base::Base;声明使用基类构造函数,但不会初始化子类成员
    using Base::Func(); 声明使用基类同名函数
 using str_map_t = std::map<std::string, val>
 str_map<int> map1;

using uint_t = unsigned int;
//定义函数
typedef void (*func_t)(int , int);
using func_t = void (*)(int , int)
//定义任意类型的模板表达式
template <typename T>
using type_t = T;
type_t<int> i;

emplace_back 减少内存拷贝和移动

push_back()向容器中加入一个右值元素(临时对象)时,首先会调用构造函数构造这个临时对象,然后需要调用拷贝构造函数将这个临时对象放入容器中。原来的临时变量释放。这样造成的问题就是临时变量申请资源的浪费
emplace_back在容器尾部添加一个元素,这个元素原地构造,不需要触发拷贝构造和转移构造。而且调用形式更加简洁,直接根据参数初始化临时对象的成员
大多数情况下优先使用emplace_back来代替push_back,所有的标准容易(除array外,因为它的长度不可改变,不能插入元素)都增加了类似的方法:emplace、emplace_hint、emplace_front、emplace_after、emplace_back
用法:

  • vector
    vector< A > v; v.emplace_back(1,2);//直接通过构造函数的参数就可以构造对象,所以要求对象有对应的构造函数
  • map
    std::map m;
    m.emplace(4, A(“dd”, 3));//对应insert用法:m.insert(std::make_pair(4, A(“dd”, 3)))

decltype关键字

decltype关键字用来在编译时推导出一个表达式的类型,语法格式decltype(exp)
int x =0; decltype(x) y = 1; //y为int
cont int & i = x; decltype(i) j = y; //j为const int &

  • 和auto区别在于:
    auto只能根据变量的初始化表达式推导出变量的类型,若想要通过表达式得到类型,但又不希望新变量和表达式具有相同的值,则可以用decltype

std::tuple

是固定大小的不同类型值的集合,是泛化的std::pair。也可以把它当做一个通用的结构体使用,不需要创建结构体又获得结构体的特征。

  • tuple< const char *, int> tp = std::make_tuple(sendPck, nSendSize);//构造一个tuple
    等价于 struct A{char * p, int len};
  • 用std:tie创建tuple
    auto tp = return std:tie(1, “aa”, 2); //tp的类型实际是:std::tuple
    const char * data = tp.get<0>(); //获取第一个值
    int len = tp.get<1>();//获取第二个值
  • std::map m;
    m.emplace( std::forward_as_tuple( 10, std::string(20,‘a’) ) );//创建了一个类似于std::tuple类型的tuple

std::make_unique

make_unique只是把参数完美转发给要创建对象的构造函数,再从new出来的原生指针,构造std::unique_ptr智能指针。这种形式的函数,不支持数组和自定义删除器。
如下的:
std::make_unique< impl::EventThread >(std::move(eventThreadSource), std::move(interceptCallback), connectionName);
相当于
std::unique_ptr(new DispSyncSource(&dispSync, phaseOffsetNs, true, connectionName));

new (op)T

是在op指向的位置上构造T

  • 防止类型收窄

int i = 1.1;
int i = {1.1};编译器会报错

override

override关键词是C++11新引入的标识,和Java中的@Override类似。
override也不是必须要写的关键词。但加上它后,编译器将做一些有用的检查
C++中,也可以阻止某个虚函数被override,方法和Java类似,就是在函数声明后添加final关键词,比如
virtual void test1(boolean test) final;//如此,test1将不能被派生类override了

initializer_list

vector< int> intvec = {1,2,3,4,5};
vector< string> strvec{”one”,”two”,”three”};”
intvect和strvect的初值由两个花括号{}和里边的元素来指定。C++11中,花括号和其中的内容就构成一个列表对象,其类型是initializer_list,也属于STL标准库。初始化列表不会引起拷贝构造函数的调用,直接初始化成员
initializer_list是一个模板类,花括号中的元素的类型就是模板类型。并且,列表中的元素的数据类型必须相同。
另外,如果类创建的对象实例构造时想支持列表方式的话,需要单独定义一个构造函数

class Test{
public:
   //①定义一个参数为initializer_list的构造函数
   Test(initializer_list<int> a_list){
     //②遍历initializer_list,它也是一种容器
     for(auto item:a_list){
       cout<<”item=<<item<<endl;
  }  } }
 Test a = {1,2,3,4};//只有Test类定义了①,才能使用列表初始化构造对象
 initializer_list<string> strlist ={1,2,3};
 using ILIter =initializer_list<string>::iterator;
//③通过iterator遍历initializer_list
for(ILIter iter =strlist.begin();iter != strlist.end();++iter){
    cout<<”item =<< *iter<< endl;
}
Foo a1{123}; 
Foo a3 = {123}; 
int a2{3};

int *a = new int{123};//指针a指向了new返回的内存,通过初始化列表方式初始化
double b = double{12.12};//对匿名对象使用列表初始化胡,在进行拷贝初始化

Foo func(void)
{
	return {123, 231.0};//return语句如同返回了一个Foo(123, 231.0) 
}

static_assert

assert,也叫断言。程序员一般在代码中一些关键地方加上assert语句用以检查参数等信息是否满足一定的要求。如果要求达不到,程序会输出一些警告语(或者直接异常退出)。总之,assert是一种程序运行时做检查的方法。
有没有一种方法可以让程序员在代码的编译期也能做一些检查呢?为此,C++11推出了static_assert,它的语法如下:
static_assert (bool_constexpr , message )
当bool_constexpr返回为false的时候,编译器将报错,报错的内容就是message。注意,这都是在编译期间做的检查。
读者可能会好奇,什么场合需要做编译期检查呢?举个最简单的例子。假设我们编写了一段代码,并且希望它只能在32位的机器上才能编译。这时就可以利用static_assert了,方法如下:
static_assert(sizeof(void*) == 4,”can only be compiled in32bit machine”);
包含上述语句的源码文件在64位机器上进行编译将出错,因为64位机器上指针的字节数是8,而不是4

std::forward和完美转发

所谓完美转发,是指在函数模板中,完全依照模板的参数的类型(即保持参数的左值、右值特征),将参数传递给函数模板中调用的另外一个函数

  • 模板构造时候使用完美转发
    template< typename … Args>
    static T* Instance(Args && … args)
    { if(m_pInstance == nullptr) m_pInstance = new T(std::forward< Args>(args)…);}

std::move

move将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存拷贝
当一个对象内部有较大的内存或者动态数组是,很有必要写move予以的拷贝构造函数和赋值函数,避免所有的深拷贝,C++中所有容易都实现了move语义
move对拥有形如对内存、文件句柄等资源的成员有效,如果是一些基本类型,比如init、char[10]数组等,如果使用move,仍然会发生拷贝,因为没有对应的移动构造函数。

右值引用&&

  • 先解释下左值右值:
    左值:表达式结束后依然存在的持久对象;看能不能对表达式取地址,变量或对象都是左值
    右值:表达式结束时就不存在的临时对象;比如非引用返回的临时变量、运算表达式产生的临时变量、lambda表达式为纯右值。将要被移动的对象、T&&函数返回值、std::move返回值、转换为T&&类型的转换函数返回值为C++11引入的将亡值
  • 右值引用&&
    对一个右值进行引用的类型,因为右值不具名,所以只能通过引用找到它
    无论左值引用还是右值引用都必须立即进行初始化,因为引用类型本身不拥有所绑定对象的内存,只是该对象的别名
  • 右值引用减少拷贝构造函数和析构函数调用,利用这个特点进行性能优化
    比如:A getA(){ return A(); } //这里调用一次拷贝构造函数
    A && a = getA(); //则拷贝构造函数在这里不再执行
    const A & a = getA();//等效,但是A & a = getA()会报错,因为非常量左值引用只能接受左值
    比如:
    template
    void f(const T&& param) //如果不加const,则是普通引用,加上const后变成一个右值引用类型
  • 右值引用性能优化,避免深拷贝
    请参考下面移动构造和移动赋值函数

移动构造和移动赋值函数

移动的含义
C++11总结_第1张图片
C++11总结_第2张图片

注:对于图中的测试函数,现在的编译器已经能做到高度优化,以至于图中列出的移动或拷贝调用都不需要了。为了达到图中的效果,编译时必须加上-fno-elide-constructors标志以禁止这种优化

C++11总结_第3张图片

如果是Base&&(两个&&符号),则表示是Base的右值引用类型。
如果是Base&(一个&符号),则表示是Base的引用类型。和右值引用相比,这种引用也叫左值引用
左值是有名字的,并且可以取地址。
右值是无名的,不能取地址。比如图19中getTemporyBase返回的那个临时对象就是无名的,它就是右值

使用移动函数的场景:
第一,如果确定被转移的对象(比如图19中的tmp对象)不再使用,就可以使用移动构造/赋值函数来提升运行效率。
第二,我们要保证移动构造/赋值函数被调用,而不是拷贝构造/赋值函数被调用。例如,上述代码中Base y = x这段代码实际上触发了拷贝构造函数,这不是我们想要的。为此,我们需要强制使用移动构造函数,方法为Base y = std::move(x)。move是std标准库提供的函数,用于将参数类型强制转换为对应的右值类型。通过move函数,我们表达了强制使用移动函数的想法。

如果类没有定义移动构造或移动赋值函数,编译器会调用对应的拷贝构造或拷贝赋值函数。所以,使用std::move不会带来什么副作用,它只是表达了要使用移动之法的愿望。
我们提供的移动构造函数的同事,也要提供拷贝构造函数,以防止移动不成功的时候还能拷贝构造

new和delete操作符重载

C++11总结_第4张图片

程序员可以重载(1)到(4)这四个函数。这四个函数是全局的,即它们不属于类的成员函数。有些new函数会抛异常,不过笔者接触的程序中都没有使用过C++中的异常,所以本书不拟讨论它们。
(5)到(8)为placement new系列函数。placement new其实就是给new操作符提供除内存大小之外(即count参数)的别的参数。比如“new(2,f)T”这样的表达式将对应调用operatornew(sizeof(T),2, f)函数,注意,这几个函数也是系统全局定义的。另外,C++规定(5)和(6)这两个函数不允许全局重载。
(9)到(12)定义为类的成员函数。注意,虽然上边的(5)和(6)不能进行全局重载,但是在类中却可以重载它们。

C++11总结_第5张图片

通过重载new和delete操作符,我们有机会在对象创建和释放的时候做一些内存管理的工作。比如,每次new一个Obj对象,我们递增new被调用的次数。delete的时候再递减。当程序退出时,我们检查该次数是否归0。如果不为0,则表示有Obj对象没有被delete,这很可能就是内存泄露的潜在原因。

lambda表达式

auto f = [ 捕获列表,英文叫capture list ] ( 函数参数 ) ->返回值类型 { 函数体 }

  • =号右边是lambda表达式,左边是变量定义,变量名为f。lambda表达式创建之后将得到一个匿名函数对象,规范中并没有明确说明这个对象的具体数据类型是什么,所以一般用auto来表示它的类型。注意,auto并不是类型名,它仅表示把具体类型的推导交给编译器来做。简而言之,lambda表达式得到的这个匿名对象是有类型的,但是类型叫什么不知道,所以程序员只好用auto来表示它的类型,反正它的具体类型会由编译器在编译时推导出来。
  • 捕获列表:lambda表达式一般在函数内部创建。它要捕获的东西也就是函数内能访问的变量(比如函数的参数,在lambda表达式创建之前所定义的变量,全局变量等)。之所以要捕获它们是因为这些变量代表了lambda创建时所对应的上下文信息,而lambda表达式执行的时候很可能要利用这些信息。
    例如:[] 不捕获任何变量;
    [&] 捕获外部作用于中所有变量,并作为引用在函数体中使用(按引用捕获)
    [=] 捕获外部作用于中所有变量,并作为副本在函数体中使用(按值捕获)
    [=, & foo] 按值捕获外部作用于中所有变量,并按引用捕获foo变量
    [this] 捕获当前列中的this指针,让lambda表达式拥有和当前类成员函数同样的访问权限,如果已经使用了&或=,就默认添加此选项。
  • 函数参数、返回值类型以及函数体:这和普通函数的定义一样。不过,lambda表达式必须使用尾置形式的函数返回声明。尾置形式的函数返回声明即是把原来位于函数参数左侧的返回值类型放到函数参数的右侧。比如,"int

C++11总结_第6张图片

容器类

容器类型 STL类名 Java类 说明
动态数组 vector ArrayList 动态大小的数组,随机访问速度快
链表 list LinkedList 一般实现为双向链表
集合 set,multiset SortedSet 有序集合,一般用红黑树来实现。set中没有值相同的多个元素,而multiset允许存储值相同的多个元素
映射表 map、multimap SortedMap 按Key排序,一般用红黑树来实现。map中不允许有Key相同的多个元素,而multimap允许存储Key相同的多个元素
哈希表 unordered_map HashedMap 映射表中的一种,对Key不排序

vector
C++11总结_第7张图片
map

C++11总结_第8张图片

map是模板类,使用它之前需要包含头文件。map模板类包含四个模板参数,第一个模板参数Key代表键值对中键的类型,第二个模板参数T代表键值对中值的类型,第三个模板参数Compare,它用于比较Key大小的,因为map是一种按key进行排序的容器。第四个参数Allocator用于分配存储键值对的内存。STL中,键值对用pair类来描述。
使用map的时候离不开pair。pair定义在头文件。pair也是模板类,有两个模板参数T1和T2

C++11总结_第9张图片

通过索引Key的方式可添加或访问元素。比如stringMap[“4”]=“four”,如果stringMap[“4”]所在的元素已经存在,则是对它重新设置新的值,否则是添加一个新的键值对元素。该元素的键为"4",值为"four"。
通过insert添加一个元素。再次强调,map中元素的类型是pair,所以必须构造一个pair对象传递给insert。C++11前可利用辅助函数make_pair来构造一个pair对象,C++11之后可以利用{}花括号来隐式构造一个pair对象了。

C++11总结_第10张图片

decltype(表达式):用于推导表达式的数据类型。比如decltype(5)得到的是int,decltype(true)得到的是bool。decltype和auto都是C++11中的关键词,它们的真实类型在编译期间由编译器推导得到。
std::function是一个模板类,它可以将一个函数(或lambda表达式)封装成一个重载了函数操作符的类。这个类的函数操作符的信息(也就是函数返回值和参数的信息)和function的模板信息一样。比如图51中"function“将得到一个类,该类重载的函数操作符为"booloperator() (int,int)”。

allocator

ava程序员在使用容器类的时候从来不会考虑容器内的元素的内存分配问题。因为Java中,所有元素(除int等基本类型外)都是new出来的,容器内部无非是保存一个类似指针这样的变量,这个变量指向了真实的元素位置。
这个问题在C++中的容器类就没有这么简单了。比如,我们在栈上构造一个string对象,然后把它加到一个vector中去。vector内部是保存这个string变量的地址,还是在内部构造一个新的存储区域,然后将string对象的内容保存起来呢?显然,我们应该选择在内部构造一个区域,这个区域存储string对象的内容。
STL所有容器类的模板参数中都有一个Allocator(译为分配器),它的作用包括分配内存、构造对应的对象,析构对象以及释放内存。STL为容器类提供了一个默认的类,即std::allocator。
C++11总结_第11张图片

allocator模板类的用法,我们可以为容器类指定自己的分配器,它只要定义图52中的allocate、construct、destory和deallocate函数即可。当然,自定义的分配器要设计好如何处理内存分配、释放等问题也是一件很考验程序员功力的事情

算法和函数对象

STL中要使用算法相关的API的话需要包含头文件< algorithm >,如果要使用一些专门的数值处理函数的话则需额外包含< numeric >头文件
常用的算法函数

函数名 作用
fill fill_n fill:为容器中指定范围的元素赋值 fill_n:为容器内指定的n个元素赋值
min/max 返回容器某范围内的最小值或最大值
copy 拷贝容器指定范围的元素到另外一个容器
accumulate 定义于,计算指定范围内元素之和
sort 对容器类的元素进行排序
binary_search 对已排序的容器进行二分查找
lexicographical_compare 按字典序对两个容器内内指定范围的元素进行比较
equal 判断两个容器是否相同(元素个数是否相等,元素内容是否相同)
remove_if 从容器中删除满足条件的元素
count 统计容器类满足条件的元素的个数
replace 替换容器类旧元素的值为指定的新元素
swap 交换两个元素的内容
all_of 检查区间[ first, last ] 中是否所有元素都满足一元条件判断式,所有都满足条件返回true,否则返回false;如:vector< int > v={1,3,5}; auto isEven = [] {int i}{return i%2 != 0}; bool isAll = std::all_of(v.begin(), v.end(), isEven);
any_of 检查区间[ first, last ] 中是否至少一个元素满足一元条件判断式,只要一个满足条件返回true,否则返回false;如:vector< int > v={1,3,5}; auto isEven = [] {int i}{return i%2 != 0}; bool isAll = std::any_of(v.begin(), v.end(), isEven);

C++11总结_第12张图片
C++11总结_第13张图片

remove_if将vector中值为-1的元素remove。但是这个元素会被remove到哪去?该元素所占的内存会不会被释放?STL中,remove_if函数只是将符合remove条件的元素挪到容器的后面去,而将不符合条件的元素往前挪。所以,vector最终的元素布局为前面是无需移动的元素,后面是被remove的元素。但是请注意,vector的元素个数并不会发生改变。所以,remove_if将返回一个迭代器位置,这个迭代器的位置指向被移动的元素的起始位置。即vector中真正有效的元素存储在begin()和newEnd之间,newEnd和end()之间是逻辑上被remove的元素。
当使用者remove_if调用完毕后,务必要通过erase来移除容器中逻辑上不再需要的元素。aIntVector.erase(newEnd,aIntVector.end());

函数对象

STL中要使用函数对象相关的API的话需要包含头文件< functional >

类或函数名 作用
bind 对可调用对象进行参数绑定以得到一个新的可调用对象。详情见正文
function 模板类,用于得到一个重载了函数调用对象的类
hash 模板类,用于计算哈希值
plus/minus/multiplies 模板类,用于计算两个变量的和,差和乘积
equal_to/greater/less 模板类,用于比较两个数是否相等或大小

std::function
是可调用对象的包装器。是一个类模板,可容纳除了类成员(函数)指针之外的所有可调用对象。通过制定他的模板参数,它可以用同意的方式处理函数、函数对象、函数指针,并允许保存和延迟执行它们
std::function最常用的是回调函数

  • 绑定普通函数: void func(void){} std::function frl = func;
  • 绑定累的静态成员函数:std::function fr2 = Foo::foo_func;
  • 回调函数:
    class A{
    std::function callback;
    A(const std::function & f) : callback(f){}
    void notify() { callback()}//执行回调
    }

C++11总结_第14张图片

std::bind

一般和std::function组合使用,作用是将可调用对象和其参数一起进行绑定。绑定后的结果可以使用std::function进行保存,并延迟调用到任何我们需要的时候
主要有两大作用:

  • 将可调用对象与其参数一起绑定成一个仿函数
  • 将多元(参数个数为n,n>1)可调用对象转换成一元或者(n-1)元可调用对象,即只绑定部分参数

C++11总结_第15张图片

void call_when_even(int x, const function<void(int)> & f){ }
void output(int x){ std::cout<<x}
//std::placeholders是一个占位符,代表这个位置将在函数调用时,被传入的第一个参数替代
auto fr = std::bind(output, std::placeholders::_1);
call_when_even(2, fr);

std::bind(ouput, 1);//输出1

智能指针类

C++11此次在STL中推出了两个比较常用的智能指针类

shared_ptr:
共享式指针管理类。内部有一个引用计数,每当有新的shared_ptr对象指向同一个被管理的内存资源时,其引用计数会递增。该内存资源直到引用计数变成0时才会被释放。

  • 不能通过直接将原始指针赋值来初始化,比如std::shared< int > ptr = new int(1);,需要通过std::make_shared或者reset方式
  • 不要在函数实参中创建shared_ptr
    如:function(std::shared_ptr< int>(new int), getA() );
    因为在C++参数的计算顺序随编译器不同而不同,有的是从左到右,有的从右到左,所以有可能是先new int,然后调用getA(),如果在getA时发生异常,而shared_ptr< int>还没创建,则int内存泄漏
  • 避免智能指针循环引用,会引起内存泄漏
  • 通过get的方式获取原始指针,如:int * p = ptr.get();
  • 指定删除器,当p引用计数为0时,自动调用删除器deleteIntPtr来释放内存,deleteIntPtr也可以是lambda表达式
    void deleteIntPtr(int * p) { delete p;}
    std::shared_ptr< int> p (new int(2), deleteIntPtr) ;
    std::shared_ptr< int> p (new int(2), [](int * p) { delete p;}) ;//lambda用法
    std::shared_ptr< int> p (new int[10], [](int * p) { delete[] p;}) ;//制指定delte[] 动态数组用法

unique_ptr
独占式指针管理类。被保护的内存资源只能赋给一个unique_ptr对象。当unique_ptr对象销毁、重置时,该内存资源被释放。一个unique_ptr源对象赋值给一个unique_ptr目标对象时,内存资源的管理从源对象转移到目标对象。

  • 不允许复制,但可以通过函数返回给其他的unique_ptr,还可以通过std::move来转移给其他的unique_ptr
    例如:unique_ptr< T > myPtr(new T);
    unique_ptr< T > otherPtr = std::move(myPtr);
    unique_ptr< T > ptr = myPtr; //错误,只能移动不能复制
  • 需要通过std::make_unique创建
  • 指定删除器,但是跟shared_ptr不同,指定删除器时,需要确定删除器类型
    std::shared_ptr< int> p (new int(2), [](int * p) { delete p;}) ;//lambda用法
    std::unqiue_ptr< int , void (*) (int *) > ptr(new int(1), [](int * p){ delete p;} )
  • shared_ptr和unqiue_ptr的思想其实都很简单,就是借助引用计数的概念来控制内存资源的生命周期。相比shared_ptr的共享式指针管理,unique_ptr的引用计数最多只能为1罢了。注意:环式引用问题
  • 虽然有shared_ptr和unique_ptr,但是C++的智能指针依然不能做到Java那样的内存自动回收。并且,shared_ptr的使用也必须非常小心,因为单纯的借助引用计数无法解决环式引用的问题,即A指向B,B指向A,但是没有别的其他对象指向A和B。这时,由于引用计数不为0,A和B都不能被释放。

weak_ptr
弱引用指针主要用来监视shared_ptr的生命周期,不会使引用计数加1,主要用来返回this指针和解决循环引用问题

  • use_count判断引用计数
    shared_ptr< int > sp(new int(10));
    weak_ptr< int > wp(sp);
    count<
  • expired方法监视的资源是否释放
    if(wp.expired()) //为真则释放
  • 通过lock方法获取监视shared_ptr
    if(!wp.expired()) audo spt = wp.lock();
  • 解决循环引用C++11总结_第16张图片

C++11总结_第17张图片
C++11总结_第18张图片

thread

void func(int a, double b){}
std::thread th(func)

  • th.join();//阻塞线程
  • th.detach();//将线程和线程对象分离,让线程作为后台线程执行,但是无法控制线程执行完
  • std::thread th1(std::move(th));//线程不能够赋值,但是可以移动
  • std::thread th2(std::bind(func, 1,2));
  • std::thread th3( [] (int a, double){ } , 1, 2);//
  • th1.get_id();//获取当前线程id
  • std::thread::hardware_concurrency();//获取CPU核数
  • 线程函数中执行 std::this_thread::sleep_for(std::chrono::seconds(3));//让线程休眠3秒

线程互斥

  • std::mutex 独占的胡吃两,不能递归使用
    try_lock()尝试锁定互斥量,如果成功返回true,非阻塞的
    尽量使用lock_guard(),超过作用域后自动释放
    std::mutex g_lock;
    void func(){
    g_lock.lock();
    //
    g_lock.unlock();
    }
  • std::recursive_mutex 递归互斥量,不带超时功能
    递归锁允许统一线程多次获得该互斥锁,可以用来解决同一线程需要多次获取互斥量时死锁问题
    std::lock_guard< std::mutex> lock(g_lock);//非递归用法
    std::lock_guard< std::recursive_mutex> lock(g_lock);//递归锁,相比非递归,效率会低
  • std::timed_mutex 带超时的独占互斥量,不能递归使用
  • std::recursive_timed_mutex 带超时的递归互斥量

条件变量

  • condition_variable 配合std::unique_lock< std:mutex > 进行wait操作
  • condition_variable_any 和任意带有lock、unlock语义的mutex搭配使用,比较灵活,但效率比condition_variable低
    某个线程满足条件之后调用notify_one或者notify_all唤醒一个或者所有等待的线程

  • std::lock_guard:方便线程对互斥量上锁。是一个模板类,模板类型可以是以上的四种互斥锁,用于自动锁定解锁,直到对象作用域结束
  • std::unique_lock:方便线程对互斥量上锁,但提供了更好的上锁和解锁控制
  • std::try_lock:尝试同时对多个互斥量上锁。
  • std::lock:可以同时对多个互斥量上锁。
  • std::call_once:如果多个线程需要同时调用某个函数,call_once可以保证多个线程对该函数只调用一次

原子变量
std::atomic< T > 使用原子变量就不需要使用互斥量来保护该变量
std::atomic< int> value;

call_once/once_flag使用

  • call_once保证一个函数仅被调用一次
    std::once_flag flag;
    void do_once(){
    std::call_once(flag, { std::count<<“call thread”; } );
    }
    std::thread t1(do_once);
    std::thread t2(do_once); //这里不对在执行do_once

内存对齐

  • 结构体对的对齐方式默认是按照结构体内存对齐数值最大的基本成员进行内存对齐的
    #pragma pack(2) 或者alignas(short)
  • malloc 对齐方式是在64位下是16字节对齐
    如下是分配内存对齐的内存块C++11总结_第19张图片

你可能感兴趣的:(技术总结)