C++11特性, 智能指针,多线程, bind函数
C++11才有的, 需要添加这个
g++ -g -Wall -std=c++11 main.cpp
POSIX 标准提供的 实现
RALL是Resource acquisition is initialization的缩写,意思是“资源获取即初始化”,其核心思想是利用C++对象生命周期的概念来控制程序的资源。它的技术原理很简单,如果希望对某个重要资源进行跟踪,那么创建一个对象,并将资源的生命周期和对象的生命周期相关联。这样一来C++自带的对象管理设施就可以来管理资源了
————————————————
http://c.biancheng.net/cplus/11/
C++11 ——— 可变参数模板
分为5类:
1. 智能指针
2. 左值|右值引用(抽象)
3. 多线程
4. STL库
5. 新增关键字
2011 年,新的 C++ 11 标准诞生,C++ 11 标准无疑是颠覆性的
C++11 最常用的新特性如下:
高优:
右值引用:基于右值引用可以实现移动语义和完美转发,消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率。
智能指针:C++11新增了std::shared_ptr、std::weak_ptr等类型的智能指针,用于解决内存管理的问题。unique_ptr,
新增STL容器array以及tuple
常用:
线程库:
枚举类:
nullptr关键字:nullptr是一种特殊类型的字面值,它可以被转换成任意其它的指针类型;而NULL一般被宏定义为0,在遇到重载时可能会出现问题。
-------------------------------‘
Lambda 表达式
auto关键字:编译器可以根据初始值自动推导出类型。但是不能用于函数传参以及数组类型的推导。
初始化列表:使用初始化列表来对类进行初始化
atomic原子操作用于多线程资源互斥操作
————————————————
委派和继承构造函数 c++11
继承构造函数的引入原因:如果基类的构造函数很多,那么子类的构造函数想要实现同样多的构造接口,必须一一调用基类的构造函数,有点麻烦
java中的super
classBase{
public:
Base();
Base(intn);
Base(conststring&s);
...
};
classDerived:publicBase{
public:
usingBase::Base;// Base's constructors are redeclared here.
};
强类型枚举 C++11
标准C++枚举类型实质上就是整型, 这就存在类型不安全隐患, 同时还存在名字空间冲突的问题. 为此C++11引入了强类型枚举.
// 标准C++枚举
enumSide{
LEFT,
RIGHT,
};
enumThing{
WRONG,
RIGHT,// RIGHT和Side中的RIGHT冲突
};
// C++11 强类型枚举
enumclassSide{
LEFT,
RIGHT,
};
enumclassThing{
WRONG,
RIGHT,// RIGHT和Side中的RIGHT不会冲突
};
C++11显式虚函数重载:override与final
在传统C++中,经常容易发现意外重载虚函数的事情:
structBase{
virtualvoidfoo();
};
structSubClass:Base{
voidfoo();
};
导致有歧义: 用的是父类的, 还是自己重新写了一个
有下列三种场景:
SubClass::foo可能是程序员加入的一个和基类虚函数恰好同名的成员函数,却被编译器当作重载虚函数
SubClass::foo可能是程序员想重载虚函数,但是因为形参列表不同导致编译器认为这是一个新定义的成员函数
当基类的虚函数Base::foo被删除后,SubClass::foo就不再重载该虚函数而摇身一变成为一个普通的成员函数
override
一旦类中的某个函数被声明为虚函数,那么在所有的派生类中它都是虚函数。一个派生类的函数如果覆盖了某个继承而来的虚函数,那么它的形参类型必须与基类函数完全一致。
C++11新标准提供了override关键字来显式地告知虚拟器进行重载,编译器将检查基类是否存在这样的虚函数,否则将无法通过编译。这样的好处是使得程序员的意图更加清晰(覆盖基类中的虚函数),如果我们使用override关键字标记了某个函数但是该函数没有覆盖已有的虚函数,此时编译器会报错。
structBase{
virtualvoidfoo(int);
};
structSubClass:Base{
virtualvoidfoo(int)override;// 合法
virtualvoidfoo(float)override;// 非法, 父类无此虚函数
};
C++11的新特性:
2.2.9 类型推导C++11
推导变量类型-auto
在c++11中,可以使用auto自动推导变量的类型
C++11 中,若变量被声明成 auto, 那它的类型就会被自动匹配成初始化表达式的类型。您可以用 auto 来复制初始化或绑定引用。不过如果使用的不恰当, 会严重影响代码可读性, 所以限制在定义局部变量的范畴.
sparse_hash_map<string,int>::iteratoriter=m.find(val);// 复杂的变量声明
autoiter=m.find(val);// auto的运用可以大幅简化代码
autoi=x.Lookup(key);// 不容易对应的类型, x的声明可能不在附近几行代码中
autox(3);// 圆括号. x的类型是int
autoy{3};// 大括号. y的类型是std::initializer_list<int>
autoz=int{3};// z的类型是int, 不是std::initializer_list<int>
C++ decltype类型推导,decltype 是“declare type”的缩写,译为“声明类型”
既然已经有了 auto 关键字,为什么还需要 decltype 关键字呢?因为 auto 并不适用于所有的自动类型推导场景,在某些特殊情况下 auto 用起来非常不方便,甚至压根无法使用,所以 decltype 关键字也被引入到 C++11 中。
C++返回值类型后置(经常和类型推导&泛型一起用)
2.2.10 列表初始化
C++11中, 任何对象类型都可以被列表初始化, 这提供了一种统一的初始化方式.
在C++11标准中,又对初始化列表的功能进行一次强化。
之前是在构造函数中通过列表初始化!
// Vector 接收了一个初始化列表。
vector<string>v{"foo","bar"};
// 不考虑细节上的微妙差别,大致上相同。
// 您可以任选其一。
vector<string>v={"foo","bar"};
// 可以配合 new 一起用。
autop=newvector<string>{"foo","bar"};
C++11 2.2.11. NULL, nullptr与0
在 C++11 的项目中,建议使用系统关键字 nullptr 代表空指针。 C++11
NULL的本质是一个宏(预处理变量),NULL也代表着0,其导致程序编写存在二义性
举例:
#include<iostream>
usingnamespacestd;
voidfunc(intx)
{
cout<<"void func(int x)"<<endl;
}
voidfunc(int*y)
{
cout<<"void func(int *y)"<<endl;
}
intmain()
{
func(NULL);
return0;
}
上面代码的编写的原意是调用void func(int* x)函数,但结果不尽人意。
由于其本质是((void*)0)和0,NULL看似会隐式类型转换为int*,调用void func(int* y)函数。
但NULL == 0无需隐式类型转换,优先于void func(int* y);。
nullptr_t 是 decltype(nullptr) 的别名,而 nullptr 是一个空指针常量类型,但并没有实际的类型名称。可以保证在任何情况下都代表空指针,并不会代表0。
因为函数重载,
NULL 宏 替换的是整型的0; nullptr c++关键字。
2.2.11 Lambda 表达式
lambda匿名函数
C++112.2.12 所有权与智能指针
什么是智能指针
背景: 在C++中没有垃圾回收机制,必须自己释放分配的内存,否则就会造成内存泄露。解决这个问题最有效的方法是使用智能指针
概念: 智能指针是存储指向动态分配(堆)对象指针的类,用于生存期的控制,能够确保在离开指针所在作用域时,自动地销毁动态分配的对象,防止内存泄露
原理: 智能指针的核心实现技术是引用计数,每使用它一次,内部引用计数加1,每析构一次内部的引用计数减1,减为0时,删除所指向的堆内存。
C++11中提供了三种智能指针,使用这些智能指针时需要引用头文件<memory>
std::shared_ptr:共享的智能指针
std::weak_ptr:弱引用的智能指针,它不共享指针,不能操作资源,是用来监视shared_ptr的。
std::unique_ptr:独占的智能指针
std::shared_ptr:共享的智能指针的使用:
三步走: 初始化, 获取, 删除器
1). 初始化
ptr1.use_count()
通过调用std::weak_ptr类提供的use_count()方法可以获得当前所观测资源的引用计数
make_shared初始化
reset初始化
2). 使用(setValue(), print() ),通过get()方法得到原始地址!
Test* t = ptr5.get();
t->setValue(1000);
t->print();
3). 指定删除器
//1.简单举例
shared_ptr<Test> ppp(new Test(100), [](Test* t) {
//释放内存
cout << "Test对象的内存被释放了......." << endl;
delete t;
});
独占的智能指针unique_ptr
1). 初始化
std::unique_ptr是一个独占型的智能指针,它不允许其他的智能指针共享其内部的指针,可以通过它的构造函数初始化一个独占智能指针对象,但是不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr。
2) . 删除器
unique_ptr指定删除器和shared_ptr指定删除器是有区别的,unique_ptr指定删除器的时候需要确定删除器的类型,所以不能像shared_ptr那样直接指定删除器
具体案例使用: 和共享指针一样, 但是比他简单!
初始化智能指针unique_ptr. unique_ptr<int> ptr1(new int(3));
使用:
//1.方法一
unique_ptr<Test>ptr3(newTest(666));
Test*pt=ptr3.get();
pt->setValue(6);
pt->print();
弱引用的智能指针weak_ptr
弱引用智能指针std::weak_ptr可以看做是shared_ptr的助手,它不管理shared_ptr内部的指针。std::weak_ptr没有重载操作符*和->,因为它不共享指针,不能操作资源,所以它的构造不会增加引用计数,析构也不会减少引用计数,
它的主要作用就是作为一个旁观者监视shared_ptr中管理的资源是否存在。
常用函数
通过调用std::weak_ptr类提供的expired()方法来判断观测的资源是否已经被释放
通过调用std::weak_ptr类提供的lock()方法来获取管理所监测资源的shared_ptr对象
通过调用std::weak_ptr类提供的reset()方法来清空对象,使其不监测任何资源
————————————————
C++11 2.2.13 多线程编程 C++11
C++11提供了对多线程的统一支持, 类库更符合C++的习惯, 使用起来更简洁, 不容易出错. C++11明确了内存模型, 但是如果不是对内存模型的行为十分清楚, 则很容易出错, 并且调试起来十分困难, 所以在非必要情况下, 建议使用默认值(std::memory_order_seq_cst).
std::thread是c++11引入标准库的多线程类,std::thread结合lambda,对于实现一些多线程的开发,会变得的更加容易
C++11最重要的特性就是支持了多线程。使得C++在并行编程时不需要依赖第三方库(可以跨平台使用)。并且在原子操作中引入了原子类的概念
thread在向函数传参:引入了一个ref函数来满足这一操作
ref和cref
对于参数传递,我们有时候会需要传递引用,但是跨线程不能直接用引用来传递,需要对参数用ref和cref传递。
threadt1(Func,ref(a));
在C++11中,Mutex共包含了四个互斥量的种类:
互斥量一般都是用作两个线程之间的互斥,一般都是用来保证同一时刻只有一个线程在访问某个变量
(1)mutex
(2)recursive_mutex
(3)time_mutex
(4)recurive_timed_mutex
4者的区别:
std::recursive_mutex就是在std::mutex的基础上支持了同一个线程多次加锁;std::recursive_timed_mutex就是上面两个的结合体。
std::mutex;// 独占的互斥量,不能递归使用。
std::timed_mutex;// 带超时的独占互斥量,不能递归使用。
std::recursive_mutex;//递归互斥量,不带超时功能。
std::recursive_timed_mutex;//带超时的递归互斥量。
智能锁
例如程序员忘记了解锁或者在加锁期间抛出了异常,那么这个锁就会一直不释放!
这个时候就需要引入c++11的智能锁了,c++11的智能锁充分的利用了c++的RAII机制,完全避免了锁不释放的场景。
lock_guard
lock_guard是C++11中定义的模板类,出作用域前,lock_guard对象要被销毁,调用析构函数自动解锁,可以有效避免死锁的问题。
unique_lock
unique_lock对象销毁时自动调用析构函数解锁,可以方便的防止死锁问题。
与lock_guard不同的是,unique_lock更加的灵活,提供了很多成员函数。
条件变量: 和java中的condition很像!
互斥量主要是用户两个线程之间的互斥,两个线程的同步还是需要使用条件变量。并且条件变量一般都是和互斥量结合使用的
原子操作: atomic
在多线程的场景下,多个线程对同一个变量进行操作,一般情况下都是需要加锁的,但是加锁解锁本身也是有开销的,所以一定程度上会影响性能,那么有没有方法既不需要加锁又可以保证线程安全呢?有!那就是原子变量
总结:
c++11标准库和pthread的对比
优点
接口简洁易用
支持RAII风格的加锁
可以跨平台
配合lambda表达式,可以非常方面的实现多线程的功能
缺点
缺少了一些thread的参数设置,比如优先级、CPU affinity
真实的项目中,很少会直接用thread,更多的是用threadpool. 一般都用线程池!
2.2.14 可移动类型和可拷贝类型
移动(move)语义 {通过移动函数初始化 move}
拷贝构造函数又分为浅拷贝和深拷贝:
浅拷贝:当类中有指针时,浅拷贝会发生错误
深拷贝:每次都是重新赋值一份,在某些情况下这种方法可能内存消耗较大
因此就产生了移动构造函数
将原来对象的东西移动到新的对象上
移动后当对象销毁时不能发生错误
移动后原对象不再指向被移动的资源,这些资源的所有权已经归属新创建的对象
move 本意为 "移动",但该函数并不能移动任何数据,它的功能很简单,就是将某个左值强制转化为右值。
在 C++11 的项目中,建议使用系统关键字 nullptr 代表空指针。 C++11
2.2.14 右值引用 C++11
只在两种情况下使用右值引用, 一种是定义类型的移动操作函数时, 另一种是定义模板函数实现完美转发的时候. 除此之外, 不要使用右值引用.
引入右值引用的主要目的是提高程序运行的效率。
有些对象在复制时需要进行深复制,深复制往往非常耗时。合理使用右值引用可以避免没有必要的深复制操作
实际开发中我们可能需要对右值进行修改,为此,C++11 标准新引入了另一种引用方式,称为右值引用,用 "&&" 表示。
int&&a=10;
a=100;
cout<<a<<endl;