文章目录
GCC和MSVC(VS的底层c++编译器)
一 . 统一列表初始化 - {}初始化
C语言是大括号初始化,C++11对列表初始化扩大了范围,可以省略赋值符号
C++中的new需要调用你的构造函数
C++11增加了vector的initializer list初始化,把一个花括号赋值给一个对象,
这个对象类型就认定为initializer list
想要让自己写的容器也支持花括号初始化,需要写一个initializer list的构造函数
int main()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
vector<int> v2 = {1, 2, 3, 4};
vector<int> v3 = { 1, 2, 3, 4, 5, 6, 7, 8};
list<int> lt1 = { 1, 2, 3, 4, 5 };
pair<string, string> kv("left", "左边");
map<string, string> dict = { { "insert", "插入" }, {"sort", "排序"}, kv, make_pair("list", "列表")};
v2 = { 10, 20, 30 };
initializer_list<int> ilt = { 1, 2, 3, 4, 5, 6, 7, 8 };
return 0;
}
隐式类型的转换
不想让临时对象的隐式类型转换发生,就在对象的构造函数前加个explicit
类模板取它的内嵌类型要使用typename,不然类模板可能娶不到,typename的意思是告诉
编译器等类模板实例化的情况下再去取
#include <iostream>
#include <string>
#include <vector>
#include <list>
#include <map>
#include <set>
#include <array>
using namespace std;
// 单参数
class A
{
public:
/*explicit A(int a)
:_a(a)
{}*/
A(int a)
:_a(a)
{}
private:
int _a;
};
// 多参数
struct Point
{
int _x;
int _y;
Point(int x, int y)
:_x(x)
, _y(y)
{
cout << "Point(int x, int y)" << endl;
}
/*explicit Point(int x, int y)
:_x(x)
, _y(y)
{
cout << "Point(int x, int y)" << endl;
}*/
};
int main()
{
单参数构造函数,支持隐式类型转换
//A aa1(1);
//A aa2 = 2;
//string s1("hello");
//string s2 = "world";
//vector<string> v;
//v.push_back(s1);
//v.push_back("world");
// C++11
Point p1(1, 2);
// 多参数构造函数,支持隐式类型转换
//Point p2 = { 1, 2 };
Point p3{ 1, 2 };
int a = 1;
int b = { 2 };
int c { 3 };
int array1[]{ 1, 2, 3, 4, 5 };
int array2[5]{ 0 };
int* ptr1 = new int[5]{1,2,3};
Point* ptr2 = new Point[2]{{1, 1}, { 2, 2 } };
return 0;
}
二 .auto 自动推导类型
头文件不展开情况下再写map这种就会很长,auto简化代码
范围for
三 .decltype
作用:可以用来推导对象的类型,需要的时候可以使用一下
C++98中有typeid拿到一个变量的类型,拿不到const值
decltype用来申请类型必须初始化
int func(int a)
{
cout << "This is Func " << endl;
return a;
}
int main()
{
const int x = 1;
double y = 1.1;
//C语言查看变量类型的方式,但是不能打印出const值
cout << typeid(x).name() << endl;
cout << typeid(y).name() << endl;
//C++11
//decltype可以取出变量类型
decltype(x) z = 2; //取到的类型通过调试可以知道是const int
decltype(x*y) i = 2.3;
decltype(&x) p; // 取到的类型就是const int*类型
//三种拿到func类型的方法
int(*pfunc)(int) = func;
auto pfunc2 = func;
decltype(pfunc2) pfunc3;
decltype(&func) pfunc4;
map<string, string> dict = { {"sort","排序"},{"recursion","递归"}};
auto it = dict.begin();
//decltype(it) copy = it;可以取到迭代器的类型
//decltype使用的一种场景
vector<decltype(it)> v;
v.push_back(it);
return 0;
}
四 .nullptr
主要解决C++中 把NULL定义成字面量0的问题,nullptr用于表示空指针
五 .STL中的一些变化
①新增了一些容器
array
鸡肋语法,食之无味弃之可惜
支持了迭代器,更好的兼容了STL容器玩法
对于越界的检查
array对越界访会强制报错,而旧的数组是一种抽查行为
array是一种调用函数,调用operator[],必检查
int main()
{
int a[10];
array<int, 10> arr;
cout << sizeof(arr) << endl;
//1、支持迭代器,更好兼容STL容器玩法
// 2、对于越界的检查
a[14] = 0;//不会报错,*(a+14) = 0 ,普通数组是抽查
arr[14] = 0;//强制报错
return 0;
}
forward_list
单链表
不支持在前面插入,支持在当前位置后面插入/删除,删除的是后面位置,很怪
比较鸡肋,相比list少了个指针
②已有容器增加一些好用或者提高效率的接口
比如:列表初始化,右值引用相关一些接口提高效率
1 .列表初始化 √
2 .类似cbegin, cend
3 .移动构造,移动赋值 √
4 .右值引用版本的插入接口 √
六 .右值引用和移动语义
移动语义其实就是进行资源转移,强转可以把左值通过move变成右值再转移
左值:一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+对它赋值
简单点:可以取地址的对象,就是左值
右值:也是一个表示数据的表达式,如:字面常量,表达式返回值,传值返回函数的返回值(这个不能是左值
引用返回)
简单点:不能取地址的对象,就是右值
右值引用左值引用对比
赋值符号左边只能出现左值,右值不能出现在赋值符号左边
左值引用:给左值取别名
右值引用:给右值取别名
左值引用能否引用右值,右值引用能否引用左值
左值引用能否引用右值?–不能直接引用,但是const左值
引用可以引用右值
右值引用能否引用左值?–不能直接引用,但是可以右值引用move
以后的左值
右值引用的变量本身是左值(跟后面完美转发有关,不是很重要)
//交叉引用
//左值引用能否引用右值
//右值引用能否引用左值
int main()
{
// 以下几个都是常见的右值
double x = 1.1, y = 2.2;
10;
x + y;
fmin(x, y);
// 以下的p、b、c、*p都是左值
int* p = new int(0);
int b = 1;
const int c = 2;
// 左值引用能否引用右值 -- 不能直接引用,但是const 左值引用可以引用右值
// void push_back(const T& x)
const int& r1 = 10;
const double& r2 = x+y;
const double& r3 = fmin(x, y);
// 右值引用能否引用左值 -- 不能直接引用,但是可以右值引用可以引用move以后左值
int*&& rr1 = move(p);
int&& rr2 = move(*p);
int&& rr3 = move(b);
const int&& rr4 = move(c);
return 0;
}
左值引用总结
1 .左值引用只能引用左值,不能引用右值
2 .但是const左值引用既可以引用左值,也可以引用右值
右值引用总结
1 .右值引用只能引用右值,不饿能引用左值
2 .但是右值引用可以引用move以后的左值
右值引用使用场景
右值引用为了弥补左值引用的不足
场景1:
左值引用做参数,基本完美的解决所有问题
场景2:
左值引用做返回值,只能解决部分问题
string& operator+=()解决了问题,对象一直在没有销毁,可以左值引用解决
string operator+( )返回一个即将销毁对象时不能用左值引用返回,只能传值返回
右值引用,如何解决operator+传值返回存在拷贝的问题呢?
C++11 将右值分为 : 纯右值,将亡值(临时对象,自定义对象)->移动构造–资源转移
s2直接把s1的资源给转移走了
构造一个对象,同时有拷贝构造和移动构造,它会调谁
传的是左值,就进行拷贝构造,右值,就进行移动构造
不要轻易地使用move(),他会把左值变成右值,下次赋值时候会进行移动构造(吸星大法)
优化
本来编译器的优化,将临时对象赋值再临时对象赋值
深拷贝的情况是生成第一个临时对象时候直接去赋值(不用生成第二个临时变量)
右值引用版本,本来生成完第一个临时变量后资源进行转移,编译器优化后直接进行资源转移,都不需要生成临时对象
容器插入接口,都会提供一个右值引用的版本
七 .完美转发
模板中的&&不叫右值引用,叫万能引用,既能接收左值,又能接收右值
模板的万能引用只是提供了能够接受同时接受左值引用和右值引用的能力
但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值
我们希望在传递过程中保持它的左值或者右值的属性,就需要使用我们下面的完美转发
std::forward()可以用来保持属性2:08
只要右值引用,再传递其他函数调用,要保持右值属性,必须使用完美转发
void Fun(int& x){ cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }
// 模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
// 模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,
// 但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值,
// 我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用我们下面学习的完美转发
template<typename T>
void PerfectForward(T&& t)
{
Fun(t);
cout << "-----------------------------" << endl;
Fun(std::forward<T>(t));
cout << endl;
}
int main()
{
PerfectForward(10); // 右值
int a;
PerfectForward(a); // 左值
PerfectForward(std::move(a)); // 右值
const int b = 8;
PerfectForward(b); // const 左值
PerfectForward(std::move(b)); // const 右值
return 0;
}
new node是开空间加初始化
malloc出来的空间未初始化,想要初始化就要调用定位new初始化,左值调拷贝构造,右值调移动构造
八 .Lambda表达式
最早是C#提出来的
可调用对象:
C 1.函数指针
C++98 2.仿函数
C++11 3.lambda
C++11 4.包装器
书写格式
lambda也可以叫匿名函数,因为它没有名字
捕捉不能重复捕捉,=其他传值捕捉,可以不传参
能定义在全局,但是不能捕捉东西,总结:在全局中不能捕捉对象
lambda表达式之间不能互相赋值,即使类型看起来一样
看一下lambda的类型
举例:有商品类,商品有名字,价格,评价
要求先按照名字,再价格,再评价排
在不用lambda表达式的情况下,我们这么排
//按价格排
struct ComparePrice
{
//从大到小排
bool operator()(const Goods& gd1,const Goods& gd2)
{
return gd1._price > gd2._price;
}
};
//按评价排
struct CompareEvalucate
{
//从小到大排
bool operator()(const Goods& gd1, const Goods& gd2)
{
return gd1._evaluate > gd2._evaluate;
}
};
int main()
{
vector<Goods> v = { {"西瓜",5.0,10},{"香蕉",7.0,8},{"草莓",5.5,12}};
sort(v.begin(), v.end(), ComparePrice());
sort(v.begin(), v.end(), CompareEvalucate());
}
使用lambda表达式
使用mutable前面捕捉的对象才能改变,参数列表不能省略
=值捕捉,&引用捕捉
可以用逗号分隔
在全局不能捕捉
int main()
{
//用lambda表达式表示两个数相加
auto res = [](int a, int b)->int{return a + b; };
//可以省略->返回类型,会自动类型推导
auto res1 = [](int a, int b){return a + b; };
cout << res1(1,2) << endl;
//捕捉列表的用处
//写一个经典的交换两个数
int a = 1, b = 2;
//一般的lambda表达式
auto swap1 = [](int& a, int& b) {
int t = a;
a = b;
b = t;
};
swap1(a, b);
cout << "a = " << a << " b = " << b << endl;
//新的写法
auto swap2 = [&a,&b]() {
int t = a;
a = b;
b = t;
};
swap2();
cout << "a = " << a << " b = " << b << endl;
//mutable取消常性,可以修改
auto swap3 = [a,b]()mutable{
int t = a;
a = b;
b = t;
};
swap3();
cout << "a = " << a << " b = " << b << endl;
return 0;
}
lambda的类型和底层原理
一串不会重复的字符串
lambda 底层原理,其实是被处理成一个lambda_uuid的一个仿函数类
struct Goods
{
string _name; // 名字
double _price; // 价格
int _evaluate; // 评价
Goods(const char* str, double price, int evaluate)
:_name(str)
, _price(price)
, _evaluate(evaluate)
{}
};
int main()
{
vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3 }, { "菠萝", 1.5, 4 } };
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._price > g2._price; });
return 0;
}
九 .新的类功能
四个关键字:
①final(使用后该类就不能被继承)
没有C++11就只能将构造函数私有,final还可以用来修饰函数,函数就不能被重写
②override(检查能否被重写)
③default(因为某些原因默认构造函数生不成了,用default可以强制编译器生成默认构造函数)
④delete
单例模式不允许一个对象拷贝,赋值
假设A的对象不允许拷贝
如果按C++98将赋值写在private中,那么默认构造也不会产生,要自己在public写A的默认构造
但在public函数中可以写拷贝构造,事情没有做绝
C++98中防拷贝做法:1 .只声明不实现 2 .声明成私有
C++11中的做法:使用关键字delete(这个函数就不能被使用了)
移动构造函数和移动赋值运算符重载
拷贝构造 – 深拷贝
移动构造 – 浅拷贝,本质是交换 – 狸猫换太子
C++11新增了两个默认成员函数:移动构造函数和移动赋值运算符重载
本来98中6个,现在变成8个
如果你自己没有实现移动 构造函数,且没有实现析构函数,拷贝构造,拷贝赋值中的任意一个,那么编译器会
自动生成一个默认移动构造。
1 .默认生成条件更复杂
2 .内置类型值拷贝,自定义类型 可以移动构造,可以拷贝构造 两种
十 .可变模板参数
printf
argument变量
Args模板参数包 (0~n个模板参数),args是一个函数形参包
参数包的展开
可以用来计算参数包中数据的个数
第一种模板解析的方式:再加一个模板参数
样例:解析并打印出每个参数的类型及值
有0个参数的需求可以写一个重载版本
列表初始化的展开
…是按照前面的方式依次展开 参数包
可以不要用逗号表达式优化一下
emplace
2:38资源转移
emplace_back支持模板的可变参数,并且万能引用
emplace_back对比push_back一个是直接构造,一个是先构造再移动构造
十一 .线程库
多线程实现X的++
- 线程对象不可以拷贝,但是可以移动
- C++11 thread库可以跨平台
看下面一段代码,看看加锁在1的位置和2的位置哪个更好?
此代码是多线程对数x进行++
#include<iostream>
#include<functional>
#include<thread>
#include<mutex>
using namespace std;
int x = 0;
mutex mtx;
void Func(int n)
{
//2 .
mtx.lock();
for (int i = 0; i < n; i++)
{
//1.
//mtx.lock();
x++;
//mtx.unlock();
}
mtx.unlock();
}
int main()
{
thread t1(Func, 50000);
thread t2(Func, 50000);
t1.join();
t2.join();
cout << x << endl;
return 0;
}
- 自旋锁可以不停询问,如果要加在1处,不要使用互斥锁,要使用自旋锁效果更佳
- 加锁加在外面,t1和t2就可以变成串行运行
- 加锁加在里面,t1和t2能交替并行运行
- 因为++x太快了,会导致t1和t2线程频繁切换上下文,实际效果不如加在外面来的好
- 但串行的方式不是最好的
原子操作
- 原子操作实际上是一种解决问题的很好的一种方式
#include<iostream>
#include<functional>
#include<thread>
#include<mutex>
#include<atomic>
#include<ctime>
using namespace std;
int main()
{
mutex mtx;
atomic<int> x = 0;
const int N = 10000;
atomic<int> costTime1 = 0;
thread t1([&](){
int begin1 = clock();
for (int i = 0; i < N; i++)
{
//mtx.lock();
x++;
//mtx.unlock();
};
int end1 = clock();
costTime1 += end1 - begin1;
});
thread t2([&]() {
int begin2 = clock();
for (int i = 0; i < N; i++)
{
//mtx.lock();
x++;
//mtx.unlock();
};
int end2 = clock();
costTime1 += end2 - begin2;
});
t1.join();
t2.join();
cout << x <<"消耗的时间是"<<costTime1<<"秒"<< endl;
return 0;
}
让N个线程对数x++M次
#include<iostream>
#include<functional>
#include<thread>
#include<mutex>
#include<atomic>
#include<ctime>
#include<vector>
using namespace std;
int main()
{
atomic<int> x = 0;
int N, M;
mutex mtx;
cin >> N >> M;
vector<thread> v;
v.resize(N);
for (int i = 0; i < v.size(); i++)
{
v[i] = thread([M, &x,&mtx] {
for (int i = 0; i < M; i++)
{
mtx.lock();
cout << std::this_thread::get_id() << "->" << x << endl;
++x;
mtx.unlock();
}
});
}
for (auto& e : v)
{
e.join();
}
cout << x << endl;
return 0;
}
thread可执行函数对象参数不能是左值引用
- 这样写会发现编译报错
void func(int& x)
{
x++;
}
int main()
{
int n = 0;
thread t1(func, n);
t1.join();
cout << n << endl;
return 0;
}
- 实在想要改变可以传地址
void func(int* x)
{
(*x)++;
}
int main()
{
int n = 0;
thread t1(func, &n);
t1.join();
cout << n << endl;
return 0;
}
- 还有一种方式,在传引用的基础上加上std::ref
void func(int& x)
{
x++;
}
int main()
{
int n = 0;
thread t1(func,std::ref(n));
t1.join();
cout << n << endl;
return 0;
}
unique_lock和lock_guard
- unique_lock和lock_guard都可以完成自动释放锁,unique_lock还多了一些丰富的功能
template<class Lock>
class LockGuard
{
public:
LockGuard(Lock& lock)
:_lock(lock)
{
_lock.lock();
}
~LockGuard()
{
_lock.unlock();
}
private:
Lock& _lock; // 注意是同一把锁,要用引用
};
void func(vector<int>& v,int n,int m,mutex& mtx)
{
try {
for (int i = 0; i < n; i++)
{
//LockGuard<mutex> lock(mtx);
lock_guard<mutex> lock(mtx);
//unique_lock<mutex> lock(mtx);
//mtx.lock();
//cout << this_thread::get_id() << ":" << m + i << endl;
//vector插入失败了 -- 异常安全的问题
v.push_back(m + i);
/*if (m == 1000 && i == 500)
throw bad_alloc();*/
//mtx.unlock();
}
}
catch (const exception& e)
{
cout << e.what() << endl;
mtx.unlock();
}
}
int main()
{
mutex mtx;
vector<int> v;
thread t1(func, std::ref(v),1000,1000,std::ref(mtx));
thread t2(func, std::ref(v),1000,2000,std::ref(mtx));
t1.join();
t2.join();
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
cout << v.size();
return 0;
}
- 条件变量一定要跟互斥锁配合着用,因为条件变量不是线程安全的
#include<iostream>
#include<functional>
#include<thread>
#include<mutex>
#include<atomic>
#include<ctime>
#include<vector>
#include<condition_variable>
using namespace std;
//两个线程交替打印奇数和偶数
int main()
{
int n = 100;
int i = 0;
mutex mtx;
condition_variable cv;
bool flag = false;//用于判断先谁执行
//打印偶数
thread t1([n, &i,&mtx,&cv,&flag] {
while (i < n)
{
unique_lock<mutex> lock(mtx);
cv.wait(lock, [&flag] {return !flag; }); //!flag返回flase的话这个wait是有效的,就会被阻塞,不然优先运行
cout << this_thread::get_id() << ":" << i << endl;
++i;
flag = true;
cv.notify_one();//唤醒一个
}
});
//交替走
//打印奇数
thread t2([n, &i,&mtx, &cv, &flag] {
while (i < n)
{
unique_lock<mutex> lock(mtx);
cv.wait(lock, [&flag] {return flag; });//同理,这里flag返回false会被阻塞,如果是true的话优先运行
cout << this_thread::get_id() << ":" << i << endl;
++i;
flag = false;
cv.notify_one();
}
});
t1.join();
t2.join();
return 0;
}
十二.包装器
普通用法function
function包装器 也叫做适配器,C++的function本质是一个类模板,也是一个包装器
- 包装器可以调用多个。可以是函数名,可以是函数对象,也可以是lambda表达式
#include<iostream>
using namespace std;
// 可调用对象类型
// 1、函数指针 void(*p)();
// 2、仿函数/函数对象
// 3、lambda表达式
template<class F, class T>
T useF(F f, T x)
{
static int count = 0;
cout << "count:" << ++count << endl;
cout << "count:" << &count << endl;
return f(x);
}
double f(double i)
{
return i / 2;
}
struct Functor
{
double operator()(double d)
{
return d / 3;
}
};
int main()
{
// 函数名
cout << useF(f, 11.11) << endl;
// 函数对象
cout << useF(Functor(), 11.11) << endl;
// lambda表达式
cout << useF([](double d)->double{ return d / 4; }, 11.11) << endl;
return 0;
}
- 包装器可以对不同的类型包装成统一的类型格式,有自己独特的优势
#include<iostream>
#include<functional>
using namespace std;
int f(int a, int b)
{
return a + b;
}
struct Functor
{
public:
int operator() (int a, int b)
{
return a * b;
}
};
class Plus
{
public:
static int plusi(int a, int b)
{
return a + b + 1;
}
double plusd(double a, double b)
{
return a + b;
}
};
int main()
{
//函数名
function<int(int, int)> f1 = f;
cout << f1(1, 2) << endl;
//函数对象
function<int(int, int)> f2 = Functor();
cout << f2(1, 2) << endl;
//类内静态函数,后面可以不加&,加上也不会错
function<int(int, int)> f3 = Plus::plusi;
cout << f3(1, 2) << endl;
//类内普通函数,模板参数里要多加一个类名,后面一定要加上&
function<double(Plus, double, double)> f4 = &Plus::plusd;
cout << f4(Plus(),1.11, 2.22) << endl;
return 0;
}
- 上上份代码的function版
template<class F, class T>
T useF(F f, T x)
{
static int count = 0;
cout << "count:" << ++count << endl;
cout << "count:" << &count << endl;
return f(x);
}
double f(double i)
{
return i / 2;
}
struct Functor
{
double operator()(double d)
{
return d / 3;
}
};
int main()
{
//函数名
function<double(double)> f1 = f;
cout << useF(f1, 1.11) << endl;
//函数对象
function<double(double)> f2 = Functor();
cout << useF(f2, 3.33) << endl;
//lamberda表达式
function<double(double)> f3 = [](double d)->double {return d / 4; };
cout << useF(f3, 4.44) << endl;
return 0;
}
bind
- 调整参数的顺序
- 库里面的函数,用着参数不舒服,可以通过绑定来改一下
- function基本用法
- function调用类内的函数
- 通过bind来调整参数的顺序
- 通过bind来调整参数的个数
int SubFunc(int a, int b)
{
return a - b;
}
class Sub
{
public:
int sub(int a, int b)
{
return a - b;
}
};
int main()
{
//function最基本的用法
function<int(int, int)> f1 = SubFunc;
cout << f1(10, 3) << endl;
//function调用类中的方法
function<int(Sub, int, int)> f2 = &Sub::sub;
cout << f2(Sub(),10, 3) << endl;
//通过bind调整参数的顺序
function<int(int, int)> f3 = bind(SubFunc, placeholders::_1, placeholders::_2);//这种是正常顺序
cout << f3(10, 3) << endl;
function<int(int, int)> f4 = bind(SubFunc, placeholders::_2, placeholders::_1);//这种是正常顺序
cout << f4(10, 3) << endl;
//通过bind调整参数的个数
function<int(int, int)> f5 = bind(&Sub::sub, Sub(),placeholders::_1, placeholders::_2);
cout << f5(10, 3) << endl;
//auto也是可以的但是不明确参数类型,还是用function开头较为清楚
auto f6 = bind(&Sub::sub, Sub(), placeholders::_1, placeholders::_2);
cout << f6(10, 3) << endl;
return 0;
}
十三.智能指针
RAII
- 遇到箭头所指的地方都可能抛异常的情况,p1出异常/p2出异常/p3出异常/div()出异常,就要根据根据不同情况进行不同处理释放相应资源,用之前try catch捕获就会很挫
- 我们引用智能指针,是一种RAII
- 基本的智能指针满足两方面,一个是满足RAII,还有一个是用起来像指针一样(满足基本的*和->)
- 智能指针最难的地方在它的拷贝构造
#include <iostream>
#include <memory>
#include <thread>
#include <mutex>
using namespace std;
//RAII
//用起来像指针一样
template<class T>
class SmartPtr
{
public:
SmartPtr(T* ptr)
:_ptr(ptr)
{}
~SmartPtr()
{
cout << "delete:" << _ptr << endl;
delete _ptr;
}
//像指针一样使用
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
int main()
{
// 如何解决呢?
SmartPtr<int> sp1(new int);
SmartPtr<int> sp2(sp1);
return 0;
}
C++98 auto_ptr
- auto_ptr是一个失败设计,很多公司明确要求不能使用auto_ptr,会进行管理权转移,原先指针会悬空
// 智能指针的发展历史
// C++98 管理权转移 auto_ptr
namespace zkx
{
template<class T>
class auto_ptr
{
public:
auto_ptr(T* ptr)
:_ptr(ptr)
{}
auto_ptr(auto_ptr<T>& sp)
:_ptr(sp._ptr)
{
// 管理权转移
sp._ptr = nullptr;
}
~auto_ptr()
{
if (_ptr)
{
cout << "delete:" << _ptr << endl;
delete _ptr;
}
}
// 像指针一样使用
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
}
// 结论:auto_ptr是一个失败设计,很多公司明确要求不能使用auto_ptr
int main()
{
std::auto_ptr<int> sp1(new int);
std::auto_ptr<int> sp2(sp1); // 管理权转移
// sp1悬空
*sp2 = 10;
cout << *sp2 << endl;
cout << *sp1 << endl;
return 0;
}
C++11更新
- boost 库中有 scoped_ptr / shared_ptr / weak_ptr
- C++11更新智能指针实现,吸收了boost库中智能指针的精华
- C++11 -> unique_ptr / shared_ptr / weak_ptr
unique_ptr / scoped_ptr
- 库里面的unique_ptr是防拷贝的
// unique_ptr/scoped_ptr
// 原理:简单粗暴 -- 防拷贝
namespace zkx
{
template<class T>
class default_delete
{
public:
void operator()(const T* ptr)
{
cout << "delete:" << ptr << endl;
delete ptr;
}
};
// 释放方式有D删除器决定
template<class T, class D = default_delete<T>>
class unique_ptr
{
public:
unique_ptr(T* ptr)
:_ptr(ptr)
{}
~unique_ptr()
{
if (_ptr)
{
//cout << "delete:" << _ptr << endl;
//delete _ptr;
D del;
del(_ptr);
}
}
// 像指针一样使用
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
unique_ptr(const unique_ptr<T>& sp) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;
private:
T* _ptr;
};
}
int main()
{
/*zkx::unique_ptr<int> sp1(new int);
zkx::unique_ptr<int> sp2(sp1);*/
std::unique_ptr<int> sp1(new int);
//std::unique_ptr<int> sp2(sp1);
return 0;
}
shared_ptr
- 引用记数
- opeerato= 赋值的时候,用指针而不是对象判可以避免自己给自己赋值的情况
- 指向对上资源的线程安全问题是访问的人处理的,智能指针不管,也管不了
- 引用计数的线程安全问题,是智能指针应该管理的
- 括一个局部的匿名域可以把锁的生命周期锁在匿名域内,这种方式比手动的unlock要好
- 问题:shared_ptr智能指针式线程安全的吗?
是的,因为引用记数的加减是加锁保护的,但是指向的资源不是线程安全的
循环引用
- 这里一个释放另一个也会释放
- 这边两个形成了一个死扣,你中有我,我中有你,永远解不开的环
- 比较忌讳你中有一个指针管我,我中有一个指针管你,遇到这种情况,weak_ptr可以出场
weak_ptr
- 不是常规意义的智能指针,没有接收原生指针的构造函数,也不符合RAII
- shared_ptr可以赋值给weak_ptr, weak_ptr也可以赋值给weak_ptr
- 解除了引用计数
- shared_ptr引用计数 和 weak_ptr引用计数对比
定制删除器
- 考试基本不考,实际使用有价值
- unique_ptr 和 shared_ptr架构不一样
- unique_ptr是在模板参数里加删除器 --类型
- shared_ptr是在构造函数的参数里给 --对象
class A
{
public:
~A()
{
cout << "~A()" << endl;
}
private:
int _a1 = 0;
int _a2 = 0;
};
template<class T>
struct DeleteArray
{
void operator()(const T* ptr)
{
cout << "delete[]:" << ptr << endl;
delete[] ptr;
}
};
struct DeleteFile
{
void operator()(FILE* ptr)
{
cout << "fclose:" << ptr << endl;
fclose(ptr);
}
};
int main()
{
//unique_ptr在模板中给 -- 类型
unique_ptr<A> up1(new A);
unique_ptr<A, DeleteArray<A>> up2(new A[10]);
unique_ptr<FILE, DeleteFile> up3(fopen("test.txt", "w"));
//shared_ptr在构造函数的参数给 -- 对象
shared_ptr<A> sp1(new A);
shared_ptr<A> sp2(new A[10], DeleteArray<A>());
shared_ptr<FILE> sp3(fopen("test.txt", "w"),DeleteFile());
//shared_ptr还可以在构造函数的参数里给lambda表达式
shared_ptr<A> ssp2(new A[10], [](A* p){delete[] p; });
shared_ptr<FILE> ssp3(fopen("test.txt", "w"), [](FILE* p) {fclose(p);});
return 0;
}