目录
使用{}进行初始化
class Date
{
public:
Date(int year, int month, int day)
:_year(year), _month(month), _day(day){}
private:
int _year;
int _month;
int _day;
};
int main()
{
int x1{ 1 }; //{}初始化
int* p = new int[4]{0}; //用于new表达式
Date d{2024,1,2}; //用于初始化对象(调用构造函数)
}
2.1auto:用来实现自动类型推断,要求必须显示初始化
int main()
{
vector<int> v1;
//vector<int>::iterator it1 = v1.begin();
auto it2 = v1.begin();//和上面等价
}
2.2decltype:将变量的类型声明为表达式的类型
int main()
{
const int x1 = 1;
double x2 = 2.2;
decltype(x1 * x2)x3;//x3为double类型
decltype(&x1)p;//p为int*类型
}
2.3nullptr:表示空指针(出于清晰和安全角度)
用于遍历集合的一种循环结构
int main()
{
vector<int> v1{ 1,2,3,4,5,6 };
//for (auto& num : v1) //加上&可以修改v1里面的元素
for(auto num : v1)
{
cout << num <<" ";
}
}
a.左值和左值引用
--左值是一个数据表达式 (可出现在赋值表达式的左边/右边) ~~>可以取地址的就是左值
--左值引用: 给左值取别名
int main()
{
// 以下的p、b、c、*p都是左值
int* p = new int(0);
int b = 1;
const int c = 2;
// 以下几个是对上面左值的左值引用
int*& rp = p;
int& rb = b;
const int& rc = c;
int& pvalue = *p;
return 0;
}
b.右值和右值引用
--右值 : 如字面常量, 表达式的返回值 (右值不能出现在赋值表达式的左边) ~~>右值不能取地址
--右值引用: 给右值取别名
int main()
{
double x = 1.1, y = 2.2;
// 以下几个都是常见的右值
10;
x + y;
fmin(x, y);
// 以下几个都是对右值的右值引用
int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = fmin(x, y);
// 这里编译会报错:error C2106: “=”: 左操作数必须为左值
10 = 1;
x + y = 1;
fmin(x, y) = 1;
return 0;
}
a.左值引用只能引用左值,但是const左值引用都可以引用
int main()
{
int a = 0, b = 1;
int& ref1 = a;
//int& ref2 = a + b; 报错
const int& ref2 = a + b;
}
b.右值引用只能引用右值,但是可以引用move后的左值
int main()
{
int a = 0, b = 1;
int&& ref1 = (a + b);
//int&& ref2 = a; 报错
int&& ref2 = move(a);
}
c.结论
左值引用: 直接减少了拷贝 1.引用传参 2.传引用返回
右值引用: 间接减少了拷贝 1.解决传值返回(将将亡值的资源转移) ~~>识别出左值还是右值(不再深拷贝, 直接移动拷贝)
a.区分左值和右值得意义
右值: 内置类型: 纯右值 ????????自定义类型: 将亡值
左值拷贝和右值拷贝的区别: 内置类型的区别不大 自定义类型的区别很大
b.左值拷贝与右值拷贝
左值拷贝: 深拷贝, 不会去修改原来的 ~~> 拷贝构造 拷贝赋值
右值拷贝: 当是将亡值的时候, 不去深拷贝, 进行资源的转移 ~~>移动构造 移动赋值
万能引用:使用模板,使其可以接收左值,右值
完美转发:std::forward,在传参得过程中,保留对象原生属性
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; }
// 1.模板中的&&而是万能引用,其既能接收左值又能接收右值。
// 2.右值引用后, 其属性变为左值,不然无法转移资源,但是还可能会出现继续往下传递的场景,
需要保持它右值的属性
// 3.std::forward 完美转发在传参的过程中保留对象原生类型属性
template<typename T>
void PerfectForward(T&& t)
{
Fun(std::forward<T>(t));
}
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;
}
a.默认成员函数:
移动构造函数和移动赋值运算符重载
b.类成员变量初始化:?C++11允许在类定义时给成员变量初始缺省值
c.强制/禁止生成默认成员函数
--default:强制生成默认成员函数
--delete:禁止生成默认成员函数(一般用于IO流, 底层有缓冲区, 不想被拷贝)
class Person
{
public:
Person(const char* name = "", int age = 0)
:_name(name)
, _age(age)
{}
//提供的拷贝构造就不会生产移动构造了
Person(const Person& p)
:_name(p._name)
, _age(p._age)
{}
//使用default指定生产移动构造
Person(Person && p) = default;
//使用delete显示该函数的生成
//Person(const Person& p) = delete;
private:
string _name;
int _age;
};
d.继承和多态中的final与override
--final修饰一个类~~>这个类不能被继承 修饰成员函数~~>这个成员函数不能被重写
--override修饰派生类的虚函数 用来检查是否被重写
// final:修饰虚函数,表示该虚函数不能再被重写
class Car
{
public:
virtual void Drive() final {}
};
class Benz :public Car
{
public:
virtual void Drive() {cout << "Benz-舒适" << endl;}
};
// override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错
class Car{
public:
virtual void Drive(){}
};
class Benz :public Car {
public:
virtual void Drive() override {cout << "Benz-舒适" << endl;}
};
// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}
1.递归函数方式展开参数包
// 递归终止函数
template <class T>
void ShowList(const T& t)
{
cout << t << endl;
}
// 展开函数
template <class T, class ...Args>
void ShowList(T value, Args... args)
{
cout << value << " ";
ShowList(args...);
}
int main()
{
ShowList(1);
ShowList(1, 'A');
ShowList(1, 'A', std::string("sort"));
return 0;
}
2.逗号表达式展开参数包
template <class T>
void PrintArg(T t)
{
cout << t << " ";
}
//展开函数
template <class ...Args>
void ShowList(Args... args)
{
int arr[] = { (PrintArg(args), 0)... };
cout << endl;
}
int main()
{
ShowList(1);
ShowList(1, 'A');
ShowList(1, 'A', std::string("sort"));
return 0;
}
展开函数中的逗号表达式:(printarg(args), 0),也是按照这个执行顺序,先执行printarg(args),再得到逗号表达式的结果0。同时还用到了C++11的另外一个特性——初始化列表,通过初始化列表来初始化一个变长数组, {(printarg(args), 0)...}将会展开成((printarg(arg1),0),(printarg(arg2),0), (printarg(arg3),0), etc... ),最终会创建一个元素值都为0的数组
1.语法
lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement}
一般不建议省略参数列表
捕捉列表: 捕捉的是外面对象的拷贝
mutable: 加上后可以修改传值过来的东西
2.lamba表达式的大小
lamba的大小是1,原因: lamba是生成仿函数的对象的类型, 并且这个仿函数是一个空类, 大小就是1个字节
lamba会被编译器处理成仿函数, 编译器会生成一个仿函数的类
lamba就相当于是仿函数, 它的所有参数都会作为哪个仿函数类的参数
注:针对每个lamba生成的类名不一样(属于不同的类)~~>不能互相赋值
3.lamba表达式的使用
--达到仿函数的作用
struct Goods
{
string _name; ?// 名字
double _price; // 价格
int _evaluate; // 评价
Goods(const char* str, double price, int evaluate)
:_name(str)
, _price(price)
, _evaluate(evaluate)
{}
};
//lamba表达式
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; });
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
return g1._price > g2._price; });
}
--捕捉列表与mutable的使用 (mutable: 易变的)
int main()
{
int x = 2, y = 3;
//传值捕捉
auto swap = [x,y]() mutable
{
int tmp = x;
x = y;
y = tmp;
};
swap();
cout << x << " " << y<< endl;
//引用捕捉
auto swap2 = [&x, &y]()
{
int tmp = x;
x = y;
y = tmp;
};
swap2();
cout << x << " " << y << endl;
//混合捕捉
auto func1 = [&x, y]() {};
//全部引用捕捉
auto func2 = [&]() {};
//全部传值捕捉
auto func3 = [=]() {};
//全部引用捕捉,x传值捕捉
auto func1 = [&,x]() {};
}
--其它
int main()
{
// 最简单的lambda表达式, 该lambda表达式没有任何意义
[] {};
// 省略参数列表和返回值类型,返回值类型由编译器推导为int
int a = 3, b = 4;
[=] {return a + 3; };
// 省略了返回值类型,无返回值类型
auto fun1 = [&](int c) {b = a + c; };
fun1(10);
cout << a << " " << b << endl;
// 各部分都很完善的lambda函数
auto fun2 = [=, &b](int c)->int {return b += a + c; };
cout << fun2(10) << endl;
// 复制捕捉x
int x = 10;
auto add_x = [x](int a) mutable { x *= 2; return a + x; };
cout << add_x(10) << endl;
return 0;
}
总结
lambda表达式实际上可以理解为无名函数,该函数无法直接调用,如果想要直接调用,可借助auto将其赋值给一个变量。
在块作用域以外的lambda函数捕捉列表必须为空
lambda表达式之间不能相互赋值,即使看起来类型相同
其本质是一个仿函数
1.function包装器:?也叫作适配器。C++中的function本质是一个类模板,也是一个包装器
std::function在头文件<functional>
// 类模板原型如下
template <class T> function; ??// undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;
模板参数说明:
Ret: 被调用函数的返回类型
Args…:被调用函数的形参
--使用:function可以对函数指针, 仿函数, lamba表达式进行包装, 提供统一的类型
int f(int a,int b){
return a + b;
}
struct Functor{
public:
int operator()(int a, int b){return a + b;}
};
int main()
{
function<int(int, int)> f1 = f;
function<int(int, int)> f2 = Functor();
function<int(int, int)> f3 = [](int a, int b) {return a + b; };
cout << f1(3,4) << endl;
map<string, function<int(int, int)>> FuncMap;
FuncMap["函数指针"] = f;
FuncMap["仿函数"] = Functor();
FuncMap["lamba表达式"] = [](int a, int b) {return a + b; };
cout << FuncMap["lamba表达式"](3, 4) << endl;
return 0;
}
成员函数的包装
--非静态的成员函数, 需要加上& 其参数列表有this指针, 写的时候也需要加上
--调用的时候, 加一个对象
class Plus
{
public:
Plus(int rate = 2)
:_rate(rate) {}
static int plusi(int a, int b) { return a + b; }//静态成员函数
double plusd(int a, int b) { return (a + b) * _rate; }//非静态成员函数
private:
int _rate = 2;
};
int main()
{
//function<int(int, int)> f1 = &Plus::plusi;
function<int(int, int)> f1 = Plus::plusi;//静态成员函数
function<double(Plus,int, int)> f2 = &Plus::plusd;//非静态成员函数
f1(3, 4);
f2(Plus(), 3, 4);
return 0;
}
2.bind包装器:是一个函数模板, 可以调整参数 ~~> 参数的顺序,个数
template <class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
// with return type (2)
template <class Ret, class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
调整参数的顺序,个数(
void Print(int a, int b){
cout << a <<" "<< b << endl;
}
int main(){
//function<void(int, int)> f1 = bind(Print, placeholders::_2, placeholders::_1);
auto f1 = bind(Print, placeholders::_2, placeholders::_1);
auto f2 = bind(Print, 1,2);
f1(10, 20); //打印20 10
f2(); //打印1 2
return 0;
}
绑定成员函数
class Sub {
public:
Sub(int x = 3) {}
int func(int a,int b){return a - b;}
};
int main()
{
Sub s;
function<int(Sub, int, int)> f1 = &Sub::func;
cout << f1(Sub(), 4, 3)<< endl;
function<int(int, int)> f2 = bind(&Sub::func, s, placeholders::_1, placeholders::_2);
cout << f2(4,3)<< endl;
function<int(Sub, int)> f3 = bind(&Sub::func, placeholders::_1, 100, placeholders::_2);
cout << f3(s, 4) << endl;
return 0;
}
3个参数, 1个参数显示的传递了
函数名 | 功能 |
thread() | 构造一个线程对象 |
thread(fn,args1,args2,...) | 构造一个线程对象,并关联线程函数fn. args1,args2,...为线程函数的参数 |
get_id() | 获取线程id |
jionable() | 线程是否还在执行,joinable代表的是一个正在执行中的线程 |
jion() | 该函数调用后会阻塞线程,当该线程结束后,主线程继续执行 |
detach() | 在创建线程对象后马上调用,用于把被创建的线程与线程对象分离开,分离的线程变为后台线程,创建的线程的"死活"就与主线程无关 |
1.线程是操作系统的一个概念, 线程对象可以关联一个线程,用来控制线程以及获取线程的状态
2.当创建一个线程对象后,没有提供线程函数,该对象实际没有对应任何线程
3.?当创建一个线程对象后,并且给线程关联线程函数,该线程就被启动,与主线程一起运行。
#include<thread>
void Thread(int a) { cout << "Thread1"<<a<< endl; }
class TF {
public:
void operator()() { cout << "Thread 3"; }
};
int main()
{
TF t;
thread t1(Thread,10);//函数指针
thread t2([]() {cout << "Thread2" << endl; });//lamba表达式
thread t3(t);//函数对象
t1.join(), t2.join(), t3.join();
return 0;
}
4.thread类是防拷贝的,不允许拷贝构造以及赋值,但是可以移动构造和移动赋值,即将一个
线程对象关联线程的状态转移给其他线程对象,转移期间不影响线程的执行
5. 可以通过jionable()函数判断线程是否是有效的,如果是以下任意情况,则线程无效
线程函数的参数是以值拷贝的方式拷贝到线程栈空间中的,因此:即使线程参数为引用类型,在
线程中修改后也不能修改外部实参,因为其实际引用的是线程栈中的拷贝,而不是外部实参。? ? ?注意:如果是类成员函数作为线程参数时,必须将this作为线程函数参数
#include <thread>
void ThreadFunc1(int& x)
{
x += 10;
}
void ThreadFunc2(int* x)
{
*x += 10;
}
int main()
{
int a = 10;
// 在线程函数中对a修改,不会影响外部实参,因为:线程函数参数虽然是引用方式,但其实际引用的是线程栈中的拷贝
thread t1(ThreadFunc1, a);
t1.join();
cout << a << endl;
// 如果想要通过形参改变外部实参时,必须借助std::ref()函数
thread t2(ThreadFunc1, std::ref(a));
t2.join();
cout << a << endl;
// 地址的拷贝
thread t3(ThreadFunc2, &a);
t3.join();
cout << a << endl;
return 0;
}
多线程最主要的问题是共享数据带来的问题(即线程安全)。如果共享数据都是只读的,那么没问
题,因为只读操作不会影响到数据,更不会涉及对数据的修改,所以所有线程都会获得同样的数
据。但是,当一个或多个线程要修改共享数据时,就会产生很多潜在的麻烦(可加锁解决)
#include <iostream>
using namespace std;
#include <thread>
#include <atomic>
atomic_long sum{ 0 };
void fun(size_t num)
{
for (size_t i = 0; i < num; ++i)
sum++; //原子操作
}
int main()
{
cout << "Before joining, sum = " << sum << std::endl;
thread t1(fun, 1000000);
thread t2(fun, 1000000);
t1.join();
t2.join();
cout << "After joining, sum = " << sum << std::endl;
return 0;
}
在C++11中,程序员不需要对原子类型变量进行加锁解锁操作,线程能够对原子类型变量互斥的
访问。更为普遍的,程序员可以使用atomic类模板,定义出需要的任意原子类型。
atmoic<T> t; ??// 声明一个类型为T的原子类型变量t
注意:原子类型通常属于"资源型"数据,多个线程只能访问单个原子类型的拷贝,因此在C++11
中,原子类型只能从其模板参数中进行构造,不允许原子类型进行拷贝构造、移动构造以及
operator=等,为了防止意外,标准库已经将atmoic模板类中的拷贝构造、移动构造、赋值运算
符重载默认删除掉了
#include <atomic>
int main()
{
atomic<int> a1(0);
//atomic<int> a2(a1); ?// 编译失败
atomic<int> a2(0);
//a2 = a1; ???????// 编译失败
return 0;
}
10.4.1 mutex的种类
1.std::mutex
函数名 | 功能 |
lock() | 上锁:锁住互斥量 |
unlock() | 解锁:释放对互斥量的所有权 |
try_lock() | 尝试锁住互斥量,如果互斥量被其它线程占有,则当前线程也不会被阻塞 |
调用lock():
调用try_lock():
2. std::recursive_mutex
其允许同一个线程对互斥量多次上锁(即递归上锁),来获得对互斥量对象的多层所有权,
释放互斥量时需要调用与该锁层次深度相同次数的 unlock(),除此之外,
std::recursive_mutex 的特性和 std::mutex 大致相同。
3. std::timed_mutex
try_lock_for():
接受一个时间范围,表示在这一段时间范围之内线程如果没有获得锁则被阻塞住(与
std::mutex 的 try_lock() 不同,try_lock 如果被调用时没有获得锁则直接返回
false),如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超
时(即在指定时间内还是没有获得锁),则返回 false。
try_lock_until():
接受一个时间点作为参数,在指定时间点未到来之前线程如果没有获得锁则被阻塞住,
如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指
定时间内还是没有获得锁),则返回 false。
4. std::recursive_timed_mutex
用于实现多线程数据同步。允许同一线程多次对锁进行加锁操作,而不会产生死锁。这意味着线程可以在不释放锁的情况下多次进入同一个互斥区域。
10.4.2 lock_guard
lock_guard类模板主要是通过RAII的方式,对其管理的互斥量进行了封装,在需要加锁的地方,只需要用上述介绍的任意互斥体实例化一个lock_guard,调用构造函数成功上锁,出作用域前,lock_guard对象要被销毁,调用析构函数自动解锁,可以有效避免死锁问题
template<class _Mutex>
class lock_guard
{
public:
// 在构造lock_gard时,_Mtx还没有被上锁
explicit lock_guard(_Mutex& _Mtx)
: _MyMutex(_Mtx)
{
_MyMutex.lock();
}
// 在构造lock_gard时,_Mtx已经被上锁,此处不需要再上锁
lock_guard(_Mutex& _Mtx, adopt_lock_t)
: _MyMutex(_Mtx)
{}
~lock_guard() _NOEXCEPT
{
_MyMutex.unlock();
}
lock_guard(const lock_guard&) = delete;
lock_guard& operator=(const lock_guard&) = delete;
private:
_Mutex& _MyMutex;
};
lock_guard的缺陷:太单一,用户没有办法对该锁进行控制,因此C++11又提供了
unique_lock。
10.4.3 unique_lock
与lock_guard不同的是,unique_lock更加的灵活,提供了更多的成员函数:
异常是一种处理错误的方式,当一个函数发现自己无法处理的错误时就可以抛出异常,让函数的
直接或间接的调用者处理这个错误
如果有一个块抛出一个异常,捕获异常的方法会使用?try?和?catch?关键字。try 块中放置可能抛
出异常的代码,try 块中的代码被称为保护代码。使用 try/catch 语句的语法如下所示:
try
{
?// 保护的标识代码
}catch( ExceptionName e1 )
{
?// catch 块
}catch( ExceptionName e2 )
{
?// catch 块
}catch( ExceptionName eN )
{
?// catch 块
}
2.1异常的抛出和捕获
异常的抛出和匹配原则:
???????在函数调用链中异常栈展开匹配原则:
double Division(int a, int b)
{
if (b == 0)
throw "Division by zero condition!";
else
return ((double)a / (double)b);
}
void Func()
{
int x, y;
cin >> x >> y;
cout << Division(x, y) << endl;
}
int main()
{
try
{
Func();
}
catch (const char* str)
{
cout << str << endl;
}
catch (...)
{
cout << "未知错误" << endl;
}
return 0;
}
2.2异常的重新抛出
有可能单个的catch不能完全处理一个异常,在进行一些校正处理以后,希望再交给更外层的调用
链函数来处理,catch则可以通过重新抛出将异常传递给更上层的函数进行处理
double Division(int a, int b)
{
// 当b == 0时抛出异常
if (b == 0)
{
throw "Division by zero condition!";
}
return (double)a / (double)b;
}
void Func()
{
// 这里可以看到如果发生除0错误抛出异常,另外下面的array没有得到释放。
// 所以这里捕获异常后并不处理异常,异常还是交给外面处理,这里捕获了再
// 重新抛出去。
int* array = new int[10];
try {
int len, time;
cin >> len >> time;
cout << Division(len, time) << endl;
}
catch (...)
{
cout << "delete []" << array << endl;
delete[] array;
throw;
}
// ...
cout << "delete []" << array << endl;
delete[] array;
}
int main()
{
try
{
Func();
}
catch (const char* errmsg)
{
cout << errmsg << endl;
}
return 0;
}
2.3异常安全
2.4 异常规范
// 这里表示这个函数会抛出A/B/C/D中的某种类型的异常
void fun() throw(A,B,C,D);
// 这里表示这个函数只会抛出bad_alloc的异常
void* operator new (std::size_t size) throw (std::bad_alloc);
// 这里表示这个函数不会抛出异常
void* operator delete (std::size_t size, void* ptr) throw();
// C++11 中新增的noexcept,表示不会抛异常
thread() noexcept;
thread (thread&& x) noexcept;
抛出的都是继承的派生类对象,捕获一个基类就可以了
(下面简单写一个封装的Exception,抛出的是基类对象,捕获的也是基类对象,根据捕获的对象打印错误信息)
class Exception
{
public:
Exception(int errid,const string& errmsg)
:_errid(errid)
,_errmsg(errmsg)
{}
int GetMid() const
{
return _errid;
}
const string& GetMsg() const
{
return _errmsg;
}
private:
int _errid;
string _errmsg;
};
double Division(int a , int b)
{
if (b == 0)
throw Exception(1, "除0错误");
else
return ((double)a / (double)b);
}
void Func()
{
int x, y;
cin >> x >> y;
cout << Division(x,y)<<endl;
}
int main()
{
try
{
Func();
}
catch (const Exception& e)
{
cout << e.GetMsg() << endl;
}
catch(...)
{
cout << "未知错误" << endl;
}
return 0;
}
抛出的都是继承的派生类对象,捕获一个基类可以处理类型不匹配的问题(避免了乱抛异常)
--抛出派生类, 使用基类捕获(天然的支持类型转换) + 多态(重写获取错误信息) 根据指向的对象来调用不同的函数(指向基类调基类的)
将基类的成员private改为protected ~~> 让子类能拿到_errmsg,_errid
继承的构造函数: 必须得调用父类的构造函数 (派生类的这几个函数, 要求必须去复用父类的)
异常的优点
异常的缺点
智能指针:用来解决内存泄漏的问题(构造的时候获取资源,析构的时候释放资源)
template<class T>
class SmartPtr
{
public:
//构造函数获取资源
SmartPtr(T* ptr)
:_ptr(ptr){}
//析构函数释放资源
~SmartPtr()
{
cout << "delete _ptr" << endl;
delete _ptr;
}
private:
T* _ptr;
};
int main()
{
SmartPtr<int> sp1(new int);
SmartPtr<int> sp2(new int);
return 0;
}
要像指针一样使用:可以解引用,访问
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
1.拷贝问题:因为智能指针是浅拷贝,释放的时候导致重复释放,需要引用计数
2.线程安全问题
3.重复引用问题
两个或多个智能指针对象相互持有对方的引用,导致它们所指向的内存资源无法被正确释放的现象
(引用计数无法减到0)如下面链表链接情况
析构后n1和n2的计数最后为1,释放不了节点
(出了作用域,n1,n2的引用计数--,但是n1的_next管着n2,n2的_pre管着n1,计数到不了0,无法释放)
(若只有n1的_next链接n2,出了作用域n2计数--,n1计数--到0,释放n1,n1的_next释放,n2计数--到0,n2释放)
1.1 unique_ptr:防拷贝
//防拷贝
unique_ptr(const unique_ptr<T>& up) = delete;
1.2 shared_ptr:引用计数
引用计数:每有一个指针指向这个资源的时候,计数++,当计数减少到0时,释放资源
a.计数问题:这个计数得是公共的
方案1:静态成员计数(不行)
静态成员变量属于所有对象, 多个对象可能管理多个资源,每个资源应该配对一个引用计数
方案2:在构造的时候,new一个计数,让这些对象指向资源也指向对象
template<class T>
class shared_ptr
{
private:
T* _ptr;
int* _pcount;
};
c.设计思想
1.引用计数
2.线程安全:加锁保护++,--
3.重复引用:weak_ptr不提供计数
namespace code
{
template<class T>
class shared_ptr
{
public:
//构造函数获取资源,析构函数释放资源
shared_ptr(T* ptr = nullptr)
:_ptr(ptr)
,_pcount(new int(1))
,_pmutex(new mutex)
{}
void AddRef()
{
_pmutex.lock();
++(*_pcount);
_pmutex.unlock();
}
void Release()
{
_pmutex.lock();
bool DeleteMutex = false;
if(--(*_pcount) == 0 && _ptr)
{
delete _ptr;
delete _pcount;
DeleteMutex = true;
}
_pmutex.unlock();
if(DeleteMutex)
{
delete _pmutex;
}
}
~shared_ptr()
{
Release();
}
//2.像指针一样使用
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T* get()const
{
return _ptr;
}
//3.引用计数解决拷贝构造问题
shared_ptr(const shared_ptr<T>& sp)
:_ptr(sp._ptr)
,_pcount(sp._pcount)
,_pmutex(sp._pmutex)
{
//拷贝 + 计数++
AddRef();
}
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
//不给自己赋值
if(_ptr != sp._ptr)
{
//释放资源
Release();
//赋值
_ptr = sp._ptr;
_pcount = sp._pcount;
_pmutex = sp._pmutex;
//计数++
AddRef();
}
return *this;
}
//4.weak_ptr解决重复引用问题
template<class T>
class weak_ptr
{
public:
weak_ptr()
:_ptr(nullptr)
{}
weak_ptr(const shared_ptr<T>& sp)
:_ptr(sp.get())
{}
weak_ptr<T>& operator=(const shared_ptr<T>& sp)
{
_ptr = sp.get();
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
private:
T* _ptr;
int* _pcount;
mutex _pmutex;
};
}