C++11
# C++11
Owner: -QVQ-
- 自动类型推导 高级for循环
- (性能)常量表达式 左值引用-移动语义-完美转发
- 可变参数模板-类-函数
- 闭包-仿函数和bind绑定器(新的调用实体)-lambda
- 智能指针
- 类的改进-继承构造-委托构造
- 线程
重载+ 、左移重载 、前置++ 、后置++、=、==:
点击查看
**加号运算符重载**
//成员函数重载+
Person operator+(Person& p2)
{
Person temp;
temp.m_A = p2.m_A + m_A;
temp.m_B = p2.m_B + m_B;
return temp;
}
**左移运算符重载**
//只能在全局函数实现左移重载
//ostream对象只能有一个
ostream& operator<<(ostream& out, Person p) {//注意当类中有堆区时这里要改成person &p
out << p.m_B;
return out;
}
**//前置++**
MyInteger& operator++() {
m_Num++;
return *this;
}
**//后置++**
MyInteger
operator++(int) {
MyInteger temp = *this;
m_Num++;
return temp;
}
**重载=**
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题
class Person
{
public:
Person(int age)
{
//将年龄数据开辟到堆区
m_Age = new int(age);
}
//重载赋值运算符
Person& operator=(Person &p)
{
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}//提供深拷贝 解决浅拷贝的问题
m_Age = new int(*p.m_Age);
return *this;//返回自身便于实现链式赋值;a=b=c;
}
~Person()
{
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
}
//年龄的指针
int *m_Age;
};
**重载==**
bool operator==(Person & p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return true;
}
else
{
return false;
}
}
**函数调用运算符重载**
- 函数调用运算符
() 也可以重载
- 由于重载后使用的方式非常像函数的调用,因此称为仿函数
- 仿函数没有固定写法,非常灵活
class MyPrint
{
public:
void operator()(string text)
{
cout << text << endl;
}
};
void test01()
{
//重载的()操作符 也称为仿函数
MyPrint myFunc;
myFunc("hello
world");
}
class MyAdd
{
public:
int operator()(int v1, int v2)
{
return v1 + v2;
}
};
void test02()
{
MyAdd add;
int ret = add(10, 10);
cout << "ret = "
<< ret << endl;
//匿名对象调用
cout << "MyAdd()(100,100) = " << MyAdd()(100, 100) << endl;
}
静态多态:函数重载和运算符重载,编译阶段确定函数地址
动态多态: 派生类和虚函数实现,运行时确定函数地址
STL大体分为六大组件,分别是:容器、算法、迭代器、仿函数(函数对象)、适配器(配接器)、空间配置器
- 容器:用于存储和管理数据。
- 算法:用于操作容器中的数据。
- 迭代器:用于遍历容器中的数据。
- 仿函数:类似于函数,可作为算法的参数传递。
- 适配器:用于将一种容器类型转换为另一种容器类型。
- 空间配置器:用于管理容器的内存分配和释放。
顺序容器、关联容器、无序容器
序列式容器
- vector:动态数组,支持随机访问,尾部插入和删除操作效率高,中间插入和删除效率低。空间预先分配,不够时扩容为原来的2倍。
- list:双向链表,支持双向遍历、插入、删除等操作,但不支持随机访问。
- deque:双端队列,支持随机访问,头尾插入和删除操作效率高,中间插入和删除效率低。空间预先分配,不够时扩容为原来的2倍。
关联容器
- set:集合,内部实现为红黑树,支持自动排序、搜索、插入和删除操作。元素必须唯一。(key 和值 value)(自平衡的二叉查找树)
- multiset:多重集合,内部实现为红黑树,支持自动排序、搜索、插入和删除操作。元素可以重复。
- map:映射,内部实现为红黑树,支持自动排序、搜索、插入和删除操作。元素由键值对构成,键必须唯一。(pair)
- multimap:多重映射,内部实现为红黑树,支持自动排序、搜索、插入和删除操作。元素由键值对构成,键可以重复。
无序关联容器
- unordered_set:集合,内部实现为哈希表,支持自动排序、搜索、插入和删除操作。元素必须唯一。
- unordered_multiset:多重集合,内部实现为哈希表,支持自动排序、搜索、插入和删除操作。元素可以重复。
- unordered_map:映射,内部实现为哈希表,支持自动排序、搜索、插入和删除操作。元素由键值对构成,键必须唯一。
- unordered_multimap:多重映射,内部实现为哈希表,支持自动排序、搜索、插入和删除操作。元素由键值对构成,键可以重复。
stack
面向对象和面向过程的区别
面向过程:
注重步骤与流程
数据和行为在代码中是分离的,不具备封装性。
适合用于功能比较简单
面向对象:
以对象为中心,以类为单位进行设计。
继承、多态、封装性。
适合用于功能比较复杂
# 自动类型推导
自动类型推导 | auto 关键字 | auto b = {1,2,3}; //b 为class std::initializer_list<int> | //不能用于函数的参数定义 int abc(auto cc) //错误 //必须初始化 auto b; //错误 //不能初始化数组 auto a[3] = {1,2,3}; //不能放入模板实例化 vector<auto> b ; //错误 | ||
---|---|---|---|---|---|
decltype | 自动推导出变量的值并作为关键字可以使用 | int b = 1;``decltype(b) a;cout << typeid(a).name()<<endl; //输出int | //匿名类型的枚举变量定义 enum{Ok,Error)flag;decltype(flag)flag2; | ||
追踪返回类型 | auto的高级用法 可以指定返回的函数类型 | //单独使用auto auto func2(int a,int b)->int{ return a+b:} //和decltype配合使用 auto func3(int a,double b)->decltype(a+b){ return a +b;} //配合模板的使用 template<class T1,class T2>auto mul(Tl &t1,T2 &t2)->decltype(t1*t2){``return t1*t2;} //如果不用auto指定就可能出现损失精度的情况,比如t1是int,t2是doubel,如果不用auto指定了int,那就损失了 精度。如果t1是int,t2是int,指定了double又会浪费空间 |
# 易用性的改进
易用性的改进 | 特性 | 描述 | 示例代码 | 注意事项 |
---|---|---|---|---|
初始化 | C++11 引入初始化列表 | 使用 `{}` 的方式调用构造函数进行初始化 | int a[]{1, 3, 5}; int i = {1}; int a{3}; int* arr = new int[3]{1, 2, 3}; | int a=1024; char b=a; // 数据丢失,类型收窄char b{a}; // 直接报错,C++11 可自动判断类型收窄问题
|
高级 for 循环 | 可以直接遍历数组 | int a[3] = {1,2,3}; for(int i : a){ cout << i; } |
- **注意**:函数参数里的数组是指针,不确定范围,不能用高级 for - **错误**:
- **正确**:
| |
静态断言 | static_assert | 编译时检查条件 | static_assert(sizeof(int) == 4, "int must be 4 bytes long"); | static_assert 里的表达式必须是常量**正确**:
**错误**:
|
noexcept 修饰符 | C++11 取消 `throw()` | 防止函数抛出异常 | void func() noexcept { } | 如果 `noexcept` 函数内抛异常,会编译报错 |
nullptr 关键字 | 解决 NULL 二义性问题 | C++11 引入 `nullptr` 作为类型安全的指针空值 |
**错误**:
fun(NULL); // NULL 可能解析为 `int`**正确**: fun(nullptr); // `nullptr` 只能赋给指针
| int a = NULL; // 通过int a = nullptr; // 语法错误
|
常量表达式 | constexpr | 允许在编译时进行计算 |
**错误**:
**正确**:
|
- `constexpr` 不能调用普通函数 - 不能使用全局变量 - 必须在使用前定义 |
C++11 线程安全 | 局部静态对象的初始化 | C++11 规定局部静态变量的初始化是 **线程安全** 的,仅在 **首次访问** 时初始化一次 |
# 用户定义
重载“”操作符的返回值,可以起到简化代码的读写
格式:返回值 operator"" _函数名(参数列表) { }
参数列表内容只能为如下:
char const * unsigned long long long double char const *, size_t
//常用
wchar_t const *, size_t char16_t const *, size_t char32_t const *, size_t
其中后四个参数列表适用于字符串,size_t会自动得到长度信息
示例:
size_t operator"" _len(char const * str, size_t size){ return size; }
使用:
cout << "mike"_len <<endl;
//4
# 原生字符串字面值(raw string literal)
使用户书写的字符串“所见即所得”
格式:R"(随意格式的字符串)"
例子:
cout << R"(s\n f)/nm)";
输出:
s\n f)/nm
# 右值引用
# 左右值引用
移动语义: 以移动而非深拷贝的方式初始化含有指针成员的类对象。移动语义指的就是将其他对象(通常是临时对象)拥有的内存资源“移为已用”。能够大幅度提高 C++ 应用程序的性能。(临时对象的维护 ( 创建和销毁 ) 对性能有严重影响。)
**左值:**表示可以取地址的、有名字的,const int &b
、int
、const int
**右值:**表示字面常量、表达式、函数的非引用返回值等,左值引用(int &a
)也是右值
左值引用:
“const 类型 &
”为 “万能”的左值引用类型,它可以接受非常量左值、常量左值、右值对其进行初始化;
int &a = 2;
// 左值引用int &a
绑定到右值2
,编译失败, err
const int &c = b;
// 常量左值引用const int &c
绑定到非常量左值b
,编译通过, ok
//相当于c变量和b变量是同一个变量空间,此时c=b=2,修改一个另一个也改变
//但是c是常量不能改变,c = 10 不行,但可以修改b,b = 10,此时c = 10
const int &b = 2;
// 常量左值引用const int &b
绑定到右值2
,编程通过, ok
//相当于为2分配了一个空间,b得到这个空间的地址
右值引用:
int && r1 = 22;
//相当于为22分配一个空间,这个空间的地址给r1,和int r1 =22
使用上没有区别
但性能上,int r1 =22
是生成22的空间和r1的空间,再把22拷贝给r1,效率低些
int && r2 = r1; //err
右值引用是不能够绑定到任何的左值的
const int&& a = 10;
//编译器不会报错,但无实际用处
//一方面右值引用主要用于移动语义和完美转发,需要修改常量右值
//另一方面常量右值引用的作用是引用一个不可修改的右值,这项工作完全可以交给常量左值引用完成
类中可以定义左值变量不初始化,类外不行
//int& a;//错误必须初始化
int& a = 1;//正确
class stu{
//stu(){}//error,必须初始化int& s
stu(int a):a_(a){}
int &a_;//可以,但必须在类的实例化时就初始化
}
2
3
4
5
6
7
8
C++左值引用和右值引用:
int&& r1 = 22; // 相当于为22分配一个空间,这个空间的地址给r1,和int r1 = 22使用上没有区别
// 但性能上,int r1 = 22是生成22的空间和r1的空间,再把22拷贝给r1,效率低些
//实际上对于这种简单值,编译器可能做了优化,对于复杂值使用右值引用才有意义
2
3
4
5
引用类型 | 非常量左值 | 常量左值 | 非常量右值 | 常量右值 | 使用场景 |
---|---|---|---|---|---|
非常量左值引用 | Y | N | N | N | 无 |
int b = 1; | |||||
int &a = b; | |||||
常量左值引用 | Y | Y | Y | Y | 常用于类中构建拷贝构造函数 |
int b =1; | |||||
const &c = b; | const int b =1; | ||||
const &a = b; | const &a = 1; | const int a = 1; | |||
const int& b= move(a); | |||||
//相当于c变量和b变量是同一个变量空间,此时c=b=2,修改一个另一个也改变 | |||||
//但是c是常量不能改变,c = 10 不行,但可以修改b,b = 10,此时c = 10 | |||||
非常量右值引用 | N | N | Y | N | 移动语义、完美转发 |
int &&a=1; | |||||
常量右值引用 | N | N | Y | Y | 无实际用途 |
const int&& i = 1; | const int a = 1; | ||||
const int&& b= move(a); |
注意:
函数返回值为右值,引用类型作为返回值为左值 无论是函数返回值为引用类型,还是外部用右值引用接收 都会延长函数内部定义的变量的生命周期,
但是这会带来一些问题!,如下是一个类作为返回值时为什么不要用引用的方式的例子
class stu{
public:
~stu(){
a = 0;
}
int a = 10;
}
//对于右值作为函数返回值
stu&& test1(void) {
stu a;
return move(a);//这里虽然调用的右值引用,但生命周期结束了调用析构函数
}
int main(){
stu&& a1 = test1();//这里a1得到的是调用了析构函数的对象,在工程项目中有极大风险
stu a1 = test1();//这是安全的,但这实际上并没有起到引用的效果,生成了两个对象,将调用两次构造析构
cout << a1.a << endl;//这里将输出0,这表示已经调用过类的析构函数了
}
//同理,对于左值作为函数返回值,也一样存在风险
stu& test1(void) {
stu a;
return a;
}
int main(){
stu& a1 = test1();//同上
stu a1 = test1();//同上
cout << a1.a << endl;//同上
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 移动语义
# 转移构造函数
对于如下类MyString
class MyString { public: MyString(const char *tmp = "abc") {//普通构造函数 len = strlen(tmp); //长度 str = new char[len+1]; //堆区申请空间 strcpy(str, tmp); //拷贝内容 cout << "普通构造函数 str = " << str << endl; } MyString(const MyString &tmp) {//拷贝构造函数 len = tmp.len; str = new char[len + 1]; strcpy(str, tmp.str); cout << "拷贝构造函数 tmp.str = " << tmp.str << endl; } //移动构造函数 //参数是非const的右值引用 MyString(MyString && t) { str = t.str; //拷贝地址,没有重新申请内存 len = t.len; //原来指针置空 t.str = NULL; cout << "移动构造函数" << endl; } MyString &operator= (const MyString &tmp) {//赋值运算符重载函数 if(&tmp == this) { return *this; } //先释放原来的内存 len = 0; delete []str; //重新申请内容 len = tmp.len; str = new char[len + 1]; strcpy(str, tmp.str); cout << "赋值运算符重载函数 tmp.str = " << tmp.str << endl; return *this; } ~MyString() {//析构函数 cout << "析构函数: "; if(str != NULL) { cout << "已操作delete, str = " << str; delete []str; str = NULL; len = 0; } cout << endl; } private: char *str = NULL; int len = 0; };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
MyString func() //返回普通对象,不是引用
{
MyString obj("mike");
return obj;
}
int main()
{
MyString &&tmp = func(); //右值引用接收
//MyString tmp = func(); //输出结果一样
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
执行结果:
普通构造函数 str = mike
移动构造函数
析构函数:
析构函数: 已操作delete, str = mike
2
3
4
用拷贝函数,MyString(MyString t)
会发生再创建MyString
的行为
用移动语义,MyString(MyString && t)
,不会发生再创建MyString
的行为,提高了性能
移动语义在堆空间的应用:
通过基于移动语义的移动构造函数,在传入函数时并没有拷贝,而是直接使用的变量,先前申请的obj
局部变量随函数func()
结束,但其中申请的堆空间给了现在的tmp
func()
中obj
申请堆空间,将内容拷贝给tmp
,tmp
申请堆空间存放内容后,obj
释放堆空间,浪费了性能
# 转移赋值函数
加上如下内容
//移动赋值函数
//参数为非const的右值引用
MyString &operator=(MyString &&tmp)
{
if(&tmp == this)
{
return *this;
}
//先释放原来的内存
len = 0;
delete []str;
//无需重新申请堆区空间
len = tmp.len;
str = tmp.str; //地址赋值
tmp.str = NULL;
cout << "移动赋值函数\n";
return *this;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 标准库函数 std::move
std::move()
函数,将左值强制转换成对应的右值
move是将对象的所有权从一个对象移到另一个对象,没有内存的搬迁或拷贝,因此可以提高效率改善性能
int a; int &&r1 = a; // 编译失败 int &&r2 = std::move(a); // 编译通过
clock_t start = clock();
abc a1= { "wewefnwngwaggw", "fagsdgadsgasdgasdga" };
for (int i = 0; i < 100300; i++) {
abc a2 = a1;
//abc&& a2 = move(a1);//上面约500个时钟,这一条约20个时钟单位
//abc& a2 = a1;//左值引用和右值引用差别不大
a2.str += "1";
a1 = move(a2);
//a1 = a2;//这一条和上一条耗时差别不大
}
clock_t end = clock();
2
3
4
5
6
7
8
9
10
11
使用:当用push_back这类函数时,会先生成一个临时对象再拷贝,从而降低了性能。emplace_back是生成临时对象并直接给这个对象
实现:
通过右值引用+折叠引用实现完美转发,传入参数,再用类型转换转为右值,
返回时用类模板的偏特化实现
template <class _Ty>
_NODISCARD constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) noexcept { // forward _Arg as movable
return static_cast<remove_reference_t<_Ty>&&>(_Arg);
}
//模板特例化
template <class _Ty>
struct remove_reference {
using type = _Ty;
using _Const_thru_ref_type = const _Ty;
};
template <class _Ty>
struct remove_reference<_Ty&> {
using type = _Ty;
using _Const_thru_ref_type = const _Ty&;
};
template <class _Ty>
struct remove_reference<_Ty&&> {
using type = _Ty;
using _Const_thru_ref_type = const _Ty&&;
};
template <class _Ty>
using remove_reference_t = typename remove_reference<_Ty>::type;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 完美转发 std::forward
适用于这样的场景:需要将一组参数原封不动的传递给另一个函数
“原封不动”不仅仅是参数的值不变,还有以下两组属性:左值/右值和 const/non-const。
完美转发就是在参数传递过程中,所有这些属性和参数值都不能改变,同时,而不产生额外的开销
对于使用而言,传入函数内的参数的使用,和在主函数的使用没有区别,并且运行结果也能保存下来。对这个参数的使用,就等同于将在主函数执行了函数内的代码一般
对于如下情景:
template<typename T>void print(T& t){
std::cout<< "Lvalue ref"<< std::endl;
}
template<typename T>void print(T&& t){
std::cout<< "Rvalue ref"<< std::endl;
}
template<typename T>void testForward(T&& v){//完美转发要求这里必须是&&右值引用
print(v);//v此时已经是个左值了,永远调用左值版本的print
print(std::forward<T>(v));//完美转发
print(std::move(v));//永远调用右值版本的print
std::cout<< "======================"<< std::endl;
}
int main(int argc,char* argv[])
{
int x= 1;
testForward(x);//实参为左值
testForward(std::move(x));//实参为右值
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
print(v);
//v此时已经是个左值了,永远调用左值版本的print
print(std::move(v));
//永远调用右值版本的print
C++11是通过引入“引用折叠”(reference collapsing)的新语言规则,并结合新的模板推导规则来完成完美转发。
# 引用折叠
对于如下问题
typedef const int T; typedef T & TR; TR &v = 1;
C++11中的引用折叠规则:
TR的类型定义 | 声明v的类型 | v的实际类型 |
---|---|---|
T & | TR | T & |
T & | TR & | T & |
T & | TR && | T & |
T && | TR | T && |
T && | TR & | T & |
T && | TR && | T && |
总结:TR和V的声明中如果出现了引用,只要有出现&,v的实际类型都是&
即T& > T&& > T
# std::forward
C++11中,std::forward可以保存参数的左值或右值特性:
#include <iostream>
using namespace std;
template <typename T> void process_value(T & val)
{
cout << "T &" << endl;
}
template <typename T> void process_value(T && val)
{
cout << "T &&" << endl;
}
template <typename T> void process_value(const T & val)
{
cout << "const T &" << endl;
}
template <typename T> void process_value(const T && val)
{
cout << "const T &&" << endl;
}
//函数 forward_value 是一个泛型函数,它将一个参数传递给另一个函数 process_value
template <typename T> void forward_value(T && val) //完美转发必须要有这个中间函数,完美转发要求参数必须是为右值引用
{
process_value( std::forward<T>(val) );//C++11中,std::forward可以保存参数的左值或右值特性
}
int main()
{
int a = 0;
const int &b = 1;
forward_value(a); //输出 T &
forward_value(b); // 输出const T &
forward_value(2); // 输出T &&
forward_value( std::move(b) ); //输出 const T &&
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 通用引用
**定义:**匹配左值表达式时,它表现的像一个左值引用;匹配右值表达式时,它表现的像一个右值引用。
构成通用引用的条件:
- 必须精确满足T&&这种形式(即使加上const也不行)
- 类型T必须是通过推断得到的(最常见的就是模板函数参数)
常量左值引用,传入的参数既可以是右值又可以是左值 通用引用也可以传入左值和右值,在这一点,他们是有共同的性质,但他们是不一样的
在C++11中,通用引用主要是为了实现在模板的推导中完美转发的功能。
例子
//r3是右值引用,其余是通用引用
int lvalue = 3;
auto&& r1 = lvalue;//左值引用
auto&& r2 = 4;//右值引用
int&& r3 = 5;//右值引用
auto&& r4 = r3; //等价于int& r4 = r3;//左值引用
std::vector<int> vect;
auto&& r5 = vect[0];//左值引用
template<typename T>
void func(T&& p);
func(lvalue);//左值引用
func(10);//右值引用
//注意是自动推导出来的才行
func<int>(10);//此时只能是右值引用
//func<int>(lvalue);//错误
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
注意:
模板类,在实例化时已经决定了类型,所以并不能算为推断
如下类型都不是通用引用
template<typename T>
class Bar
{
private:
T _m;
public:
Bar(T&& t); //不是通用引用,而是右值引用
};
template <class T, class Allocator = allocator<T> >
class vector
{
public:
void push_back(T&& x); //不是通用引用,而是右值引用
};
//vector类型是确定的,所以不是通用引用
template<typename S>
void func(std::vector<S>&& p);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
通用引用类型的本身是一个左值,
C++11中的通用引用 (opens new window)
# 可变参数模板
# 可变参数模板函数
# 定义
template<class ... T>
void func(T ... args)
{//可变参数模板函数
T a[] = {args...};//可以实现把参数都拷贝给数组a,
int length = sizeof...(args)//得到数组大小
//sizeof...(sizeof后面有3个小点)计算变参个数
cout<<length;
}
int main()
{
func(); // 0
func(1); // 1
func(2, 1.0); // 2
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 参数包的展开
递归方式展开
通过递归函数展开参数包,需要提供递归终止函数
//递归终止函数
void debug()
{
cout << "empty\n";
}
//展开函数
template <class T, class ... Args>
void debug(T first, Args ... last)
{
cout << "parameter " << first << endl;
debug(last...);
}
//递归调用
debug(1, 2, 3, 4);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//上诉代码递归调用顺序如下:
debug(1, 2, 3, 4); debug(2, 3, 4); debug(3, 4); debug(4); debug();
非递归方法展开:
方式一:
对于
template <class T>
T print(T arg)
{
cout << arg;
return arg;
}
template <class ... Args>
void expand(Args ... args)
{
int a[] = {print(args)...};
}
int main()
{
expand(1, 2, 3, 4);
return 0;
}//输出1234
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
方式二:
template <class T>
void print(T arg)
{
cout << arg << endl;
}
template <class ... Args>
void expand(Args ... args)
{
int a[] = { (print(args), 0)... };//逗号表达式
}
int main()
{
expand(1, 2, 3, 4);
return 0;
}//输出1234
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 可变参数模板类
# 继承方式展开参数包
template<typename... A> class BMW{}; // 变长模板的声明
template<typename Head, typename... Tail> // 递归的偏特化定义
class BMW<Head, Tail...> : public BMW<Tail...>
{//当实例化对象时,则会引起基类的递归构造
public:
BMW()
{
printf("type: %s\n", typeid(Head).name());
}
Head head;
};
template<> class BMW<>{}; // 边界条件
int main()
{
BMW<int, char, float> car;
/*
运行结果:
type: float
type: cchar
type: int
*/
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
先Head=int,char和float作为Tail传入基类public BMW<Tail...>
Head=char,float作为Tail传入基类public BMW<Tail...>
Head=float, 调用边界条件template<> class BMW<>{}
弹出栈,开始构建
# 模板递归和特化方式展开参数包
template <long... nums> struct Multiply;// 变长模板的声明
template <long first, long... last>
struct Multiply<first, last...> // 变长模板类
{
static const long val = first * Multiply<last...>::val;
};
template<>
struct Multiply<> // 边界条件
{
static const long val = 1;
};
int main()
{
cout << Multiply<2, 3, 4, 5>::val << endl; // 120
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 闭包
# 定义
带有上下文的函数,属于有状态的函数(仿佛就是类换了个名字),有权访问另一个函数作用域中变量的函数
一个函数,带上了一个状态(即这个闭包有自己的变量),就变成了闭包了
闭包的状态捆绑,必须发生在运行时
闭包(closure)是使用了它的外部函数局部变量的函数。
具体来讲,如果 A 是一个函数,而 B 是在 A 中定义的一个函数(表达式),且在 B 的函数体中使用了 A 的局部变量 a,那么就将函数 B 称为闭包。
特点:
- 变量常驻在内存
- 可以避免使用全局变量污染命名空间
- 内存消耗大
# 仿函数:重载(不是c++11的内容)
类中对()这个操作符的重载
class MyFunctor{
int operator()() { cout<<"hello" }
}
使用:
MyFunctor a;
//MyFunctor a()
就成调用构造函数了
a();
//hello
# std::bind绑定器
# std::function模板
#include <functional>
可调用实体主要包括:函数、函数指针、函数引用、可以隐式转换为函数指定的对象,或者实现了opetator()的对象。
C++11中,新增加了一个std::function
类模板,它是对C++中现有的可调用实体的一种类型安全的包裹。通过指定它的模板参数,它可以用统一的方式处理函数、函数对象、函数指针,并允许保存和延迟执行它们。
最大的用处就是在实现函数回调,函数回调允许将一个函数作为参数传递给另一个函数,以便在后者执行时调用前者。这种技巧通常用于实现事件处理、回调函数等功能。
它不能被用来检查相等或者不相等,但是可以与NULL或者nullptr进行比较
示例:
绑定一个普通函数:
void func(void);
function< void(void) > f1 = func;//绑定
2
绑定一个类中的静态函数:
class Foo{
public:
static int foo_func(int a){};
}
function< int(int) > f2 = Foo::foo_func;//绑定
2
3
4
5
绑定一个仿函数:
class Bar{
public:
int operator()(int a){};
}
Bar obj;
f2 = obj;//绑定
f2(222); //调用
2
3
4
5
6
7
# std::bind模板
#include <functional>
它可以预先把指定可调用实体的某些参数绑定到已有的变量,产生一个新的可调用实体
C++98中,bind1st和bind2nd,分别可以用来绑定functor的第一个和第二个参数,限制太大,可用性低
C++11中,提供了std::bind,它绑定的参数的个数不受限制,绑定的具体哪些参数也不受限制
定义:bind(可调用实体,可调用实体所在的类实例(可以没有),参数列表……)
void func(int x, int y)
{
cout << x << " " << y << endl;
}
int main()
{
bind(func, 1, 2)(); //输出:1 2
bind(func, std::placeholders::_1, 2)(1);//输出:1 2
using namespace std::placeholders; // adds visibility of _1, _2, _3,...
bind(func, 2, _1)(1); //输出:2 1
bind(func, 2, _2)(1, 2); //输出:2 2
bind(func, _1, _2)(1, 2); //输出:1 2
bind(func,_2, _1)(1, 2); //输出:2 1
//err, 调用时没有第二个参数
//bind(func, 2, _2)(1);
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//std::placeholders::_1是占位符,表示这个位置将在函数调用时,被传入的第一个参数所替代。
//std::placeholders::_2是占位符,被传入的第二个参数所替代
测试代码
#include <iostream> #include <functional> using namespace std; void f(int& a, int& b, int& c) { cout << "in function a = " << a << " b = " << b << " c = " << c << endl; cout << "in function a = " << &a << " b = " << &b << " c = " << &c << endl; a += 1; b += 10; c += 100; } int main() { int n1 = 1, n2 = 10, n3 = 100; function<void()> f1 = bind(f, n1, n2, ref(n3)); f1(); cout << "out function a = " << n1 << " b = " << n2 << " c = " << n3 << endl; cout << "out function a = " << &n1 << " b = " << &n2 << " c = " << &n3 << endl; f1(); cout << "out function a = " << n1 << " b = " << n2 << " c = " << n3 << endl; cout << "out function a = " << &n1 << " b = " << &n2 << " c = " << &n3 << endl; f1(); f1(); f1(); f1(); f1(); f1(); f1(); f1(); cout << "out function a = " << n1 << " b = " << n2 << " c = " << n3 << endl; cout << "out function a = " << &n1 << " b = " << &n2 << " c = " << &n3 << endl; return 0; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# std::bind模板和std::function模板配合
通过std::bind和std::function配合使用,所有的可调用对象均有了统一的操作方法
这里的function<int(int)>
其中的参数列表只是代表你在调用的时候需要传入的参数,和在后面bind
里的参数个数和类型没有关系。
创建:int abc(int a, int b) { return a + b; }
function<int()> f5 = bind(&abc, 2, 1);
调用:f5()
如果函数内没有使用这个参数,即使传入了也没有任何意义
但如果bind
里有一个占位符,那就必须有一个传入参数,这里的占位符不能初始化
创建:function<int(int)> f4 = bind([](int a, char b) { return b+a; }, placeholders::_1, 'a');
调用:f4(2)
class Test
{
public:
int i = 0;
void func(int x, int y)
{
cout << x << " " << y << endl;
}
};
int main()
{
Test obj; //创建对象
//函数对象的调用
function<void(int, int)> f1 = bind(&Test::func, &obj, _1, _2);
f1(1, 2); //输出:1 2
function< int &()> f2 = bind(&Test::i, &obj);
f2() = 123;//不懂为什么变量指针能作为函数调用
cout << obj.i << endl;//结果为 123
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
std::bind模板与引用:
对于如下代码,
void func1(int a, int& b, int& c) {
a += 1; //a为值拷贝传递,此时调用结果不保存,不影响外部n2的值
b += 10; //b为引用传入,调用结果保存在 function<void()> f1自己的地址中,先当于b为静态变量,成了函数的一个状态
//但b的地址和外部main函数中n2地址不同,所以不影响外部n2的值
c += 100; //c为移动传入,使用的是外部变量n2的地址空间,所以此次修改能保存下来,并影响外部n2的值
}
int main() {
int n1 = 1;
int n2 = 10;
int n3 = 100;
function<void()> f1 = bind(func1, n1, n2, ref(n3));
cout<<n1;//1
cout<<n2;//10
cout<<n3;//200
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# lambda表达式
# 定义
格式:[捕获列表](参数列表) mutable exception → 返回类型{函数体;};
[捕获列表]
:
标识一个lambda的开始,不能省略
到函数定义为止所在作用范围内可见的局部变量(包括所在类的this)
其中可以有如下形式:
空
= 函数体内可以使用到函数定义为止所在作用范围内可见的局部变量(包括所在类的this),值传递方式
lambda内不能对值传递的参数修改
& 函数体内可以使用到函数定义为止所在作用范围内可见的局部变量(包括所在类的this),引用传递方式
this 函数体内可以使用lambda所在类中的成员变量。是可修改类型的
a 将a按值传递,默认a为不可修改类型(const),如果要修改添加mutable修饰符。
&a。将a按引用进行传递。
a, &b。将a按值进行传递,b按引用进行传递。
=,&a, &b。除a和b按引用进行传递外,其他参数都按值进行传递
&, a, b。除a和b按值进行传递外,其他参数都按引用进行传递
(参数列表)
:
标识重载的()操作符的参数,可以省略。参数可以通过按值(如:(a,b))和按引用(如:(&a,&b))两种方式进行传递。
mutable
:可修改标识符
可省略,[捕获列表]
的传入按值传递的参数时,加上mutable
修饰符后,可修改按值传入来的拷贝
exception
:错误抛出标示符
可省略, 用于指定函数抛出的异常,如抛出整数类型的异常,可以使用throw(int)
返回类型
:
标识函数返回值的类型,当返回值为void或只有一处return时(编译器可自动推断出返回值类型),可省略
基本例子:
class Test
{
public:
int i = 0;
void func(int x, int y)
{
auto x1 = []{ return i; }; //err, 没有捕获外部变量
auto x2 = [=]{ return i+x+y; }; //ok, 值传递方式捕获所有外部变量
auto x4 = [this]{ return ++i; }; //ok, 捕获this指针,无关引用或值传递全局变量的传入即可修改
auto x5 = [this]{ return i+x+y; }; //err, 没有捕获x, y
auto x6 = [this, x, y]{ return i+x+y; };//ok, 捕获this指针, x, y
auto x7 = [x]{x++; };//err,不可修改
auto x8 = [x] mutable {x++; };//ok,可修改
auto x9 = [&x] {x++;}; //ok,可修改
auto x10 = [this]{ return i++; }; //ok, 捕获this指针, 并修改成员的值
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
**注意:**值传递进lambda表达式,一经传入即使外部变量的值改变了,lambda表达式内的变量不会改变
int j = 13;
auto val = [=] { return j;};
auto ref = [&] { return j;};
j++;
cout << "val: " << val() << endl;//val: 13 //值传递后,一经传入不会随外界值的改变而改变
cout << "ref: " << ref() << endl;//ref: 14 //引用传递后,会随外界值的改变而改变
2
3
4
5
6
# lambda与仿函数
在使用上是一样的,但lambda更安全和一次性
除去在语法层面上的不同,lambda和仿函数有着相同的内涵——都可以捕获一些变量作为初始化状态,并接受参数进行运行。
而事实上,仿函数是编译器实现lambda的一种方式,通过编译器都是把lambda表达式转化为一个仿函数对象。因此,在C++11中,lambda可以视为仿函数的一种等价形式。
class MyFunctor
{
public:
MyFunctor(int tmp) : round(tmp) {}
int operator()(int tmp) { return tmp + round; }
private:
int round;
};
int main()
{
//仿函数
int round = 2;
MyFunctor f1(round);//调用构造函数
cout << "result1 = " << f1(1) << endl; //operator()(int tmp)
//lambda表达式
auto f2 = [=](int tmp) -> int { return tmp + round; } ;
cout << "result2 = " << f2(1) << endl;
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# lambda类型的存储和操作
lambda表达式的类型在C++11中被称为“闭包类型”,每一个lambda表达式则会产生一个临时对象(右值)。因此,严格地将,lambda函数并非函数指针。
C++11标准允许没有捕获任何变量的lambda表达式向函数指针的转换
但函数指针无法转换为lambda
使用std::function和std::bind来存储和操作lambda表达式:
int main()
{
//使用std::function和std::bind来存储和操作lambda表达式
function<int(int)> f1 = [](int a) { return a; };
function<int()> f2 = bind([](int a){ return a; }, 123);
function<int(int)> f3 = bind([](int a) { return a; }, placeholders::_1);
cout << "f1 = " << f1(123) << endl;
cout << "f2 = " << f2() << endl;
cout << "f3 = " << f3(123) << endl;
}
2
3
4
5
6
7
8
9
10
11
12
使用自动类型 推导来存储和操作lambda表达式:
auto f3 = [](int x, int y)->int{ return x + y; };
//lambda表达式,没有捕获任何外部变量
decltype(f3) p3 = f3;
// 通过decltype获得lambda的类型
使用函数指针
int (*abc)(int a, int b) = [](int x, int y)->int { return x + y; };
//函数指针类型
cout <<abc(1,2) << endl;
# 意义
就地封装短小的功能闭包,lambda函数比较轻便,即用即扔,很适合需要完成某一项只在此一处使用的简单功能
对于如下数组内的元素,只输出偶数,功能的实现:
vector<int> nums{0,1,2,3,4,5,6,7,8,9};
- 普通遍历
for (int i : nums) if (i%2 == 0) cout<<i;
- 仿函数和算法for_each
class LNums
{
public:
void operator () (int i) const{//仿函数
if (i%2 == 0) cout<<i
}
}
for_each(nums.begin(), nums.end(), LNums());//第三个参数生成一个LNums类的实例,再传入后变为仿函数,作为谓词
2
3
4
5
6
7
8
9
- lambda函数和算法for_each
for_each(nums.begin(), nums.end(), [=](){
if (i%2 == 0) cout<<i
})
# 包装器(外覆器)wrapper
- 模板bind可替代bind1st和bind2nd,且更灵活
- 模板mem_fn让您能够将成员函数 (opens new window)作为常规函数进行传递
- 模板reference_wrapper让您能够创建像引用但可被复制的对象
- 包装器function让您能够以统一的方式处理多种类似于函数的形式
# function
为什么使用function:模版下,函数调用类型太丰富(函数名、函数指针、函数对象或有名称的lambda表达式),对模版实例化时,不同调用类型可能会生成不同的调用对象,从而导致:
- 编译时效率低:增加了编译时间和生成的代码量。
- 运行时效率低:不同类型的可调用对象可能有不同的调用约定,这可能导致运行时的额外开销。
使用包装器可以将这些不同类型的可调用对象统一,从而简化模板的实现,提高编译和运行效率。
不同的调用约定会增加开销:
- 不同的调用约定可能需要额外的指令、不同的寄存器来传递参数、执行额外的栈操作
- 无法应用某些优化技术
不使用包装器的代码
#include <iostream> template <typename T, typename F> T use_f(T v, F f) { static int count = 0; count++; std::cout << " use_f count = " << count << ", &count = " << &count << std::endl; return f(v); } class Fp { private: double z_; public: Fp(double z = 1.0) : z_(z) {} double operator()(double p) { return z_ * p; } }; class Fq { private: double z_; public: Fq(double z = 1.0) : z_(z) {} double operator()(double q) { return z_ + q; } }; #include "somedefs.h" #include <iostream> #include <functional> double dub(double x) { return 2.0 * x; } double square(double x) { return x * x; } int main() { using namespace std; double y = 1.21; //此处实例化了五个use_f模板函数,导致编译代码多,效率低 cout << "普通版本******************************************************" << endl; cout << "Function pointer dub:\n"; cout << " " << use_f(y, dub) << endl; cout << "Function pointer square:\n"; cout << " " << use_f(y, square) << endl; cout << "Function object Fp:\n"; cout << " " << use_f(y, Fp(5.0)) << endl; cout << "Function object Fq:\n"; cout << " " << use_f(y, Fq(5.0)) << endl; cout << "Lambda expression 1:\n"; cout << " " << use_f(y, [](double u) {return u * u; }) << endl; cout << "Lambda expression 2:\n"; cout << " " << use_f(y, [](double u) {return u + u / 2.0; }) << endl; cout << "优化版本******************************************************" << endl; //考虑到上述函数的参数和返回值(call signature)都为 double(double), //因此使用function<double(double)>装饰它创建六个包装器,实现只实例化一个use_f函数模板 function<double(double)> ef1 = dub; function<double(double)> ef2 = square; function<double(double)> ef3 = Fq(10.0); function<double(double)> ef4 = Fp(10.0); function<double(double)> ef5 = [](double u) {return u * u; }; function<double(double)> ef6 = [](double u) {return u + u / 2.0; }; cout << "Function pointer dub:\n"; cout << " " << use_f(y, ef1) << endl; cout << "Function pointer square:\n"; cout << " " << use_f(y, ef2) << endl; cout << "Function object Fp:\n"; cout << " " << use_f(y, ef3) << endl; cout << "Function object Fq:\n"; cout << " " << use_f(y, ef4) << endl; cout << "Lambda expression 1:\n"; cout << " " << use_f(y, ef5) << endl; cout << "Lambda expression 2:\n"; cout << " " << use_f(y, ef6) << endl; return 0; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74使用包装器的代码
#include <iostream> #include <functional> //使用function模板 template <typename T> T use_f(T v, std::function<T(T)> f) { static int count = 0; count++; std::cout << " use_f count = " << count << ", &count = " << &count << std::endl; return f(v); } class Fp { private: double z_; public: Fp(double z = 1.0) : z_(z) {} double operator()(double p) { return z_ * p; } }; class Fq { private: double z_; public: Fq(double z = 1.0) : z_(z) {} double operator()(double q) { return z_ + q; } }; #include "somedefs.h" double dub(double x) { return 2.0 * x; } double square(double x) { return x * x; } int main() { using namespace std; double y = 1.21; typedef function<double(double)> fdd; // simplify the type declaration cout << "Function pointer dub:\n"; cout << use_f(y, fdd(dub)) << endl; // create and initialize object to dub cout << "Function pointer square:\n"; cout << use_f(y, fdd(square)) << endl; cout << "Function object Fp:\n"; cout << use_f(y, fdd(Fp(5.0))) << endl; cout << "Function object Fq:\n"; cout << use_f(y, fdd(Fq(5.0))) << endl; cout << "Lambda expression 1:\n"; cout << use_f(y, fdd([](double u) {return u * u; })) << endl; cout << "Lambda expression 2:\n"; cout << use_f(y, fdd([](double u) {return u + u / 2.0; })) << endl; return 0; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# 智能指针
#include <memory>
智能指针通过使用引用计数来跟踪指向对象的指针数量。当引用计数为零时,智能指针会自动释放对象。这样,智能指针可以避免内存泄漏。
# unique_ptr
初始化:
unique_ptr<int> up1(new int(11));
// 无法复制的unique_ptr
转移所有权:
unique_ptr持有对对象的独有权,同一时刻只能有一个unique_ptr指向给定对象(通过禁止拷贝语义、只有移动语义来实现)。
//unique_ptr<int> up2 = up1; // err, 不能通过编译
//std::unique_ptr<int> sp2(sp1);// err, 不能通过编译
//以右值的方式可以转移独有权
unique_ptr<int> up3 = move(up1); // 现在p3是数据的唯一的unique_ptr,up1置空
//同理,可以通过函数的返回值来转移
std::unique_ptr<int> func(int val)
{
std::unique_ptr<int> up(new int(val));
return up;
}
std::unique_ptr<int> sp1 = func(123);
2
3
4
5
6
7
8
9
10
11
12
13
shared_ptr (const shared_ptr<U>& x, element_type* p)
//别名构造
**释放空间:**离开作用域时,若其指向对象,则将其所指对象销毁(默认使用delete操作符,用户可指定其他操作)。
up3.reset(new int(44));
//"绑定"动态对象
up3.reset();
// 显式释放内存,无返回值
p.reset(q.d);
//将p中内置指针换为q,并且用d来释放p之前所指的空间
up3 = nullptr;
//同上等价
unique_ptr<int> up4;
up4.reset();
// 对一个没有分配的指针释放空间,不会导致运行时错误
释放控制权:
int *p = up5.release();
//不会释放内存,shared_ptr不能用这个
up5.reset();
判断:
up5.unique();
判断是否只有up5指向这个地址,空指针返回false
up5.bool();
//等价up5.get()==NULL
返回值是否是空指针
访问:
cout<<*up3;
//和一般指针一样的访问
int* p = new int(10);
unique_ptr<int> up1;
up1.reset(p);
up1.reset(new int(30));
cout << *p;//乱码,空间被释放了
2
3
4
5
element_type* get() const noexcept;
返回存储的指针地址
int* p = new int (10);
std::shared_ptr<int> a (p); //此时a.get()==p
// 下面三个输出的值都是10
std::cout << *a.get() << "\n";
std::cout << *a << "\n";
std::cout << *p << "\n";
2
3
4
5
6
空间处理:
void swap (shared_ptr& x) noexcept;
std::shared_ptr<int> foo (new int(10));
std::shared_ptr<int> bar (new int(20));
foo.swap(bar);
2
3
void reset (U* p);
将p所指向的空间计数器-1,重新给p分配空间
shared_ptr<int> sp;
sp.reset(new int);
*sp = 1;
shared_ptr<int> sp2 = sp;
sp.reset(new int);
*sp = 2;//sp为2,sp1为1
2
3
4
5
6
# shared_ptr
相当于硬链接
允许多个该智能指针共享“拥有”同一堆分配对象的内存,这通过引用计数(reference counting)实现
打印计数器wp.use_count()
shared_ptr<int> p;
shared_ptr<int> q = p;
2
3
没有release()
shared_ptr<int> sp1(new int(22));
shared_ptr<int> sp2 = sp1;
cout << "count: " << sp2.use_count() << endl; //打印引用计数
sp1.reset(); //显式让引用计数减1
2
3
4
5
//不能对一个指针多次加入shared_ptr,因为shared_ptr析构时再次释放了已经释放了的空间
int* p = new int(10);
shared_ptr<int> sp1(p);
shared_ptr<int> sp2(p);//程序中断
2
3
4
//释放掉加入了共享指针的指针空间会直接报错,也是因为shared_ptr析构时再次释放了已经释放了的空间
int* p = new int(10);
shared_ptr<int> sp1(p);
shared_ptr<int> sp2 = sp1;
delete p;
2
3
4
5
总结:普通指针最好不要和智能指针复用
可以自定义删除器
std::shared_ptr<int> p7(new int[10], [](int* p) {delete[]p; });
//这里的第二个参数就是释放空间时的规则
# 使用的一些问题
- 交叉引用,使用weak_ptr避免
- 多线程问题
- 左值引用智能指针
void test(const std::shared_ptr<A>& sp1) {
cout << sp1.use_count() << endl;//输出1,
//即以左值引用的方式使用智能智能,并不会使得引用计数器+1
//那么如果外部释放了这个资源,这里将访问释放了的地址
}
int main()
{
std::shared_ptr<A> sp1(new A());
test(sp1);
}
2
3
4
5
6
7
8
9
10
11
int main()
{
std::shared_ptr<T> sp1(new T());
const auto& sp2 = sp1;//左值引用,并不会增加计数器
sp1.reset();//此时对象以及释放掉
//由于sp2已经不再持有对象的引用,程序会在这里出现意外的行为
sp2->doSomething();
return 0;
}
2
3
4
5
6
7
8
9
10
11
# weak_ptr
相当于软链接
它可以从一个shared_ptr或另一个weak_ptr对象构造(只能对shared_ptr的数据结构操作),它的构造和析构不会引起引用计数的增加或减少。
打印计数器wp.use_count()
wp.reset()
//参数只能为空,即不能重新分配内存,它的释放空间只影响自己不能访问这个空间
没有release()
访问:它可以指向shared_ptr指针指向的对象内存,却并不拥有该内存,而使用weak_ptr成员lock,则可返回其指向内存的一个share_ptr对象,且在所指对象内存已经无效时,返回指针空值nullptr。
*wp.lock();
原子的返回这个地址的shared_ptr指针
wp.expired()
返回这个指针是否释放了
- 对于weak_ptr的使用
//error
if(!wp.expired())wp->setValue(1);
//true
auto s = wp.lock();
if(s)s->setValue(1);
2
3
4
5
- 循环引用是指两个或多个对象之间互相持有对方的引用,导致对象无法被正确地释放而造成内存泄漏。
weak_ptr可以避免循环引用的情况
//一个循环引用的例子
class A;//前置声明
class B {
public:
std::shared_ptr<A> aPtr;
};
class A {
public:
std::shared_ptr<B> bPtr;
};
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a.bptr = b;
b.aptr = a;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# make_shared
创建返回一个shared_ptr对象
template <class T, class... Args> shared_ptr<T> make_shared(Args&&... args);
args为多个构造函数参数
在语义上和shared_ptr相同
优点:
- 减少内存分配次数,提高性能:
shared_ptr<widget> sp(new widget);//分配2次内存,
//先分配一次内存给widget,再分配一次内存给智能指针对象(往里面填入widget对象的指针)
shared_ptr<widget> sp = make_shared<widget>();//分配一次内存
2
3
- 潜在的资源泄露
因为内存编排不确定代码的执行顺序,对于如下语句
shared_ptr<widget> sp(new widget);
computerPriority();//如果这个函数会发生崩溃
2
如果实际的执行顺序为:
- new widget
- computerPriority()
- 执行shared_ptr的构造函数
那么computerPriority()
函数异常中止时,第三步不会执行,则已经分配的new对象无法析构掉
而使用make_shared
则不会出现这样的问题
缺点:
- 不能用{}的方式初始化make函数的对象
- 不能指定删除器
如果想指定删除器,又想避免内存泄露,则
shared_ptr<widget> sp(new widget,[](){
//删除器的代码
});
computerPriority(move(sp));//即使函数内不使用这个值,目的是为了强行让编译器的代码执行顺序确定下来
2
3
4
# make_unique
//shared_ptr分配成功时,Example分配失败,则发生了内存泄漏
auto sp = std::shared_ptr<Example>(new Example(argument));
//安全,但是效率更低,做了两次分配
auto song = new Song(L"Ode to Joy", L"Beethoven");
std::shared_ptr<Song> sp0(song);
//更安全和更有效率
auto msp = std::make_shared<Example>(argument);
2
3
4
5
6
7
8
9
创建unique_ptr并将其返回到指定类型的对象,该对象通过指定的参数进行构造
// make_unique<T>
template <class T, class... Args>
unique_ptr<T> make_unique(Args&&... args);//用于单个对象的构造
// make_unique<T[]>
template <class T>
unique_ptr<T> make_unique(size_t size);//用于数组的构造
// make_unique<T[N]> disallowed
template <class T, class... Args>
/* unspecified */ make_unique(Args&&...) = delete; //不如vector
2
3
4
5
6
7
8
9
10
11
例子
unique_ptr<Animal> p1 = make_unique<Animal>();
unique_ptr<Animal[]> p3 = make_unique<Animal[]>(5);//数组的构造
2
# 智能指针的自定义释放函数
//智能指针模板只有两个参数,第二个参数为删除器的函数指针类型
std::unique_ptr<Socket, void(*)(Socket * pSocket)> spSocket(new Socket(), [](Socket* pSocket) {
//关闭句柄
pSocket->close();
//TODO: 你甚至可以在这里打印一行日志...
delete pSocket;
});
2
3
4
5
6
7
# shared_from_this和对象保活
当一个类想返回包含自己的一个shared_ptr对象时
enable_shared_from_this<T>
是一个模板类,使用时使目标类继承这个类
class A : public std::enable_shared_from_this<A>//使对象类继承enable_shared_from_this
{
public:
A() {}
~A(){}
std::shared_ptr<A> getSelf()
{
//return make_shared<A>(*this);//使用这种方式,实际上返回的是一个独立的shared_ptr对象
//这会使得有两个shared_ptr对象都以为自己独占这个地址,从而导致两次释放同一个地址空间
return shared_from_this();//更安全
}
};
int main()
{
std::shared_ptr<A> sp1(new A());
std::shared_ptr<A> sp2 = sp1->getSelf();
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
使用场景:
在回调函数中拿到传递的参数时,通过对象的getSelf()
拿到一个安全的智能指针,并使用
注意一些问题:
- 使用getSelf()的对象不要是栈空间释放的,不然会出现多次释放同一空间
int main()
{
A a;//作用域结束后释放空间
std::shared_ptr<A> sp2 = a.getSelf();//作用域结束后也释放空间
return 0;
}
2
3
4
5
6
- 避免循环引用
class A : public std::enable_shared_from_this<A>
{
public:
A(){}
~A(){}
void func(){
m_SelfPtr = shared_from_this();
}
public:
std::shared_ptr<A> m_SelfPtr;//对象自己管理自己,引用计数器永远不能为0
};
int main()
{
std::shared_ptr<A> spa(new A());
spa->func();//对象自己管理自己,循环引用
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 智能指针对象大小
unique_ptr类型大小等同于指针大小
shared_ptr类型大小=指针大小*2
# 模板的优化
# 编译器对模板的右尖括号做单独处理
问题:
连续两个右尖括号(>>)会被编译解释成右移操作符,而不是模板参数表的形式
C++98标准是让程序员在>>之间填上一个空格
template <int i> class X{};
template <class T> class Y{};
int main()
{
Y<X<1> > x1; // ok, 编译成功
Y<X<2>> x2; // err, 编译失败
return 0;
};
2
3
4
5
6
7
8
9
10
在C++11标准中,要求编译器对模板的右尖括号做单独处理,使编译器能够正确判断出">>"是一个右移操作符还是模板参数表的结束标记。
# 模板的特化和偏特化
模板的实例化(instantiation)指函数模板(类模板)生成模板函数(模板类)的过程
模板参数在某种特定类型下的具体实现称为模板的特化
偏特化是指将部分参数类型指定为具体类型
**SFINAE:**当将模板形参替换为显式指定的类型或推导的类型失败时,从重载集中丢弃这个特化,而非导致编译失败
# 模板的别名
#include <type_traits>
//std::is_same
对类型起别名:
using uint = unsigned int; typedef unsigned int UINT;
判断类型是否一致
is_same<uint, UINT>::value
//一致返回1
# 函数模板的默认模板参数
1、普通函数带默认参数,c++98 编译通过,c++11 编译通过
void DefParm(int m = 3) {}
2、类模板是支持默认的模板参数,c++98 编译通过,c++11 编译通过
template <typename T = int>
class DefClass {};
3、函数模板的默认模板参数, c++98 - 编译失败,c++11 - 编译通过
template <typename T = int> void DefTempParm() {}
类模板的默认模板参数必须从右往左定义,函数模板的默认模板参数则没这个限定:
template<class T, int i = 0> class DefClass3;
template<int i = 0, class T> class DefClass4; // 无法通过编译
template<class T1 = int, class T2> void DefFunc1(T1 a, T2 b);
template<int i = 0, class T> void DefFunc2(T a);
2
3
4
5