文章目录
一、简介
是一种抽象类型,由类模板来实现,借由类别的析构函数来达到自动释放指针所指向的存储器或对象;
- 支持自动、异常安全的对象生命周期管理;
二、四种智能指针
1、auto_ptr/scored_ptr
该智能指针管理通过`new`创建auto_ptr获得的对象,并在其自身被`销毁时删除该对象`;
【注意】
> - 不可用多个指向`同一个对象`,当使用`拷贝构造`或`赋值运算符`时,右值会变为`nullptr`;
> - 不可用于容器中,若对象被赋值或拷贝,则会导致为空;
> - 在C++11被弃用,C++17被删除;
而scored_ptr的拷贝构造和赋值操作被delete,相比于auto_ptr更安全;
2、unique_ptr
template<
class T,
class Deleter = std::default_delete<T>
> class unique_ptr;
通过指针拥有和管理对象,且对该对象具有
唯一权
,没有其他操作能删除该对象;
- 该智能指针禁止
拷贝
和赋值
操作,但可通过std::move
移动赋值;- 可通过
reset
进行更改;- 对于数组对象提供
[]
运算符;
2.1 测试案例
#include <iostream>
#include <memory>
#include <string>
using namespace std;
class A{
public:
A(int a) : m_a(a) {
cout << "A..." << endl;
}
~A() {
cout << "Bye..." << endl;
}
int getA() const {
return m_a;
}
private:
int m_a;
};
创建
void test_unique_ptr() {
// 方式1
std::unique_ptr<A> u_a(new A(1));
// 方式2
std::unique_ptr<A> u_b;
u_b.reset(new A(2));
// 方式3
std::unique_ptr<A> u_c = std::make_unique<A>(3);
cout << "u_a: " << u_a->getA() << endl;
cout << "u_b: " << u_b->getA() << endl;
cout << "u_c: " << u_c->getA() << endl;
// std::move
std::unique_ptr<A> u_d = std::move(u_c);
cout << "u_d : " << u_d ->getA() << endl;
}
reset
void test_unique_ptr() {
std::unique_ptr<A> u_b(new A(2));
u_b.reset(new A(4)); // 会先释放内存
cout << "u_b: " << u_b->getA() << endl;
}
2.2 使用场景
- 忘记delete;
- 代码异常安全;
- 传参和返回;
3、weak_ptr
template <class T> class weak_ptr;
弱管理对象的类型,提供了对其管理的资源的一个
访问手段
,为协助shared_ptr
;
- 析构和构造不会
影响引用计数
;- 可以解决两个shared_ptr互相引用造成的
死锁问题(导致资源不会释放)
;- 该智能指针没有重载
*
以及->
,故不能直接操作对象
,避免引发错误(由于已被释放);
3.1 提供的成员函数
operator=
- 不会获得所有权,且不会增加计数;
- shared_ptr可以直接分配给
weak_ptr
;- 当一个
weak_ptr
分配给shared_ptr
需要使用lock
来完成;
void test_weak_ptr(){
std::shared_ptr<A> ptr1(new A(1));
std::shared_ptr<A> ptr2;
std::weak_ptr<A> wptr;
// 将shared_ptr转换为weak_ptr
wptr = ptr1;
// 将weak_ptr转换为shared_ptr
ptr2 = wptr.lock();
cout << "ptr1:" << &(*ptr1) << endl;
cout << "ptr2:" << &(*ptr2) << endl;
}
swap
交换对象;
void test_weak_ptr(){
std::shared_ptr<A> ptr1(new A(1));
std::shared_ptr<A> ptr2(new A(2));
std::weak_ptr<A> wptr1 = ptr1;
std::weak_ptr<A> wptr2 = ptr2;
wptr1.swap(wptr2);
cout << "ptr1: " << &(*ptr1) << endl;
cout << "ptr2: " << &(*ptr2) << endl;
cout << "wptr1: " << &(*wptr1.lock()) << endl;
cout << "wptr2: " << &(*wptr2.lock()) << endl;
}
reset
重置变为空;
void test_weak_ptr(){
std::shared_ptr<A> ptr1(new A(1));
std::weak_ptr<A> wptr1 = ptr1;
wptr1.reset();
std::cout << "wptr1 " << (wptr1.expired() ? "" : "没有") << " 过期\n";
}
use_count
共享所有权的shared_ptr对象的
数量
;
weak_ptr
不计算在内;
expired
- 检查是否过期, weak_ptr对象是否为空;
lock
若未过期,返回一个shared_ptr,包含由weak_ptr对象保留的信息;
3.2 应用场景
- 辅助shared_ptr的使用;
- 弱回调;
4、shared_ptr
对象具有获得指针
所有权
并共享
该所有权的能力,持有的资源能在多个shared_ptr之间共享;
- 当多一次对该对象的引用,则引用计数
加一
,若发生析构
,则减一
;- 当引用计数
为0
时,则释放
该资源;
4.1 常用成员函数
operator=
- 复制:使该对象的引用计数加1;
- 移动:则原对象将变为空;
void test_shared_ptr() {
std::shared_ptr<A> ptr1(new A(1));
std::shared_ptr<A> ptr2;
// copy
cout << "ptr1:" << ptr1.use_count() << endl;
cout << "ptr2:" << ptr2.use_count() << endl;
ptr2 = ptr1;
cout << "ptr1:" << ptr1.use_count() << endl;
cout << "ptr2:" << ptr2.use_count() << endl;
// move
ptr2 = std::make_shared<A>(2);
cout << "ptr1:" << ptr1.use_count() << endl;
cout << "ptr2:" << ptr2.use_count() << endl;
}
swap
交换托管对象内容,不会破坏或更改引用计数;
void test_shared_ptr() {
std::shared_ptr<A> ptr1(new A(1));
std::shared_ptr<A> ptr2(new A(2));
cout << "ptr1 count: " << ptr1.use_count() << " ptr1: " << ptr1 << endl;
cout << "ptr2 count: " << ptr2.use_count() << " ptr2: " << ptr2 << endl;
ptr1.swap(ptr2);
cout << "ptr1 count: " << ptr1.use_count() << " ptr1: " << ptr1 << endl;
cout << "ptr2 count: " << ptr2.use_count() << " ptr2: " << ptr2 << endl;
}
reset
重置指针,若原先由托管,则会将原先的删除;
- 若
不传入参数
,被置为0;- 若
传入参数
,会被置为1;
void test_shared_ptr() {
std::shared_ptr<A> ptr1(new A(1));
ptr1.reset();
cout << "ptr1引用计数: " << ptr1.use_count() << endl;
ptr1.reset(new A(2));
cout << "ptr1引用计数: " << ptr1.use_count() << endl;
}
get
获取指针;
void test_shared_ptr() {
A *a = new A(2);
std::shared_ptr<A> ptr1(a);
cout << "ptr1:" << ptr1.use_count() << endl;
if (a == ptr1.get()) {
cout << "equal..." << endl;
}
}
operator*
解引用;
void test_shared_ptr() {
A *a = new A(2);
std::shared_ptr<A> ptr1(a);
cout << "ptr1:" << ptr1.use_count() << endl;
if (a == ptr1.get()) {
cout << "equal..." << endl;
}
if (&(*ptr1) == a){
cout << "equal..." << endl;
}
}
operator->
取消引用对象成员,即直接调用对象成员;
void test_shared_ptr() {
A *a = new A(2);
std::shared_ptr<A> ptr1(a);
cout << a->getA() << endl;
}
use_count
获取引用计数;
unique
检查是否唯一;
operator bool
检查是否不为空;
operators ==, !=, <, <=, >, >=
重载了运算符;
operator<<
make_shared
制作shared_ptr;
4.2 引用计数
当计数器为
0
时,智能指针将会自动释放
;
何时计数器会递增
- 当使用拷贝时
shared_ptr<T> p(q)
p时q的拷贝,计数器会递增;- 当两个智能指针相互转换时,
左边
的指针对象会增加
;- 当它作为参数传递时;
- 当它作为函数返回值时;
何时计数器会递减
- 当两个智能指针
相互转换
时,右边的指针对象会递减
;- 当智能指针被销毁时;
使用new返回的指针来初始化智能指针;
- 一般初始化智能指针的指针必须指向动态内存;
- 由于之智能指针内部的构造函数是
explicit
,不能进行隐式转换,必须使用直接初始化
;- 由于不清楚对象何时销毁,则最好不使用内置指针来访问一个智能指针且不使用
get()
初始化或赋值;
shared_ptr<T> p(new T(1));
4.3 share_ptr与new结合使用
当new与share_ptr作为参数时
// 函数声明
int func();
void test(std::shared_ptr<int> p, int func);
由于智能指针内部的构造函数是
explicit
,故不能直接传入new
创建的指针;
// 可考虑使用以下:
test(std::shared_ptr<int> (new int), func());
第一个参数被分为俩部分:
std::shared_ptr<int>
与new int
;
由于编译器考虑高效的操作,故传入参数的执行顺序:
- 执行
new int
;- 调用
func
;- 调用
智能指针构造
;
但需要考虑到,万一
func
函数内部出现异常,则此时将会引起内存泄漏,由于new int
返回的指针遗失;
故将实参一先单独出来:
std::shared_ptr<int> p(new int);
test(p, func());
4.4 enable_shared_from_this
在子类中继承该类,能够让子类的对象创建指向自身的shared_ptr实例;
- 获取返回会增加引用计数;
class A : public enable_shared_from_this<A>{
public:
A(int a) : m_a(a) {
cout << "A..." << endl;
}
~A() {
cout << "Bye..." << endl;
}
shared_ptr<A> GetThis() {
return shared_from_this();
}
int getA() const {
return m_a;
}
private:
int m_a;
};
void test_shared_ptr() {
std::shared_ptr<A> ptr(new A(1));
ptr->GetThis();
cout << ptr.use_count() << endl;
}
注意事项
- 不能将栈对象,来获取
shared_from_this
,会导致程序奔溃;
void test_shared_ptr() {
A a(1);
auto p = a.GetThis();
}
4.6 make_shared的使用
shared_ptr内部有两个指针,其中一个是我们new传入的指针,另一个是指向包含uses以及weaks的指针;
若通过构造函数去创建一个shared_ptr,则在shared_ptr的构造函数会在次调用new去初始化引用计数的指针,可能会导致第
二次new失败,出现错误;
- 当shared_ptr引用计数为0时,则可将第一块内存释放调用尽管weaks不为0;
而make_shared是直接将两个内存一起分配的不会出现问题;
-防止资源泄漏的风险;
-内存使用效率高;
-但该方法无法自定义删除器;
-导致托管的资源延迟释放,由于是同一块内存,当weaks不为0时,他是不会被释放的;
4.5 使用场景
- shared_ptr通常使用在
共享权不明
的场景。有可能多个对象同时管理同一个内存时。对象的延迟销毁
。陈硕在《Linux多线程服务器端编程》中提到,当一个对象的析构非常耗时,甚至影响到了关键线程的速度。可以使用BlockingQueue<std::shared_ptr>将对象转移到另外一个线程中释放
,从而解放关键线程。
5、注意事项
- 分清楚什么情况下使用哪一种指针:
- 当该资源只用在当前且不共享的情况下,使用
unique_ptr
;- 当智能指针不需要管理对象的生命周期下,使用
weak_ptr
;定义
的地方使用强智能指针
,引用
对象的地址使用弱智能指针
;- 若要其他地方共享使用,使用
shared_ptr
;- 注意使用时,该资源是否
有效
;- 作为类成员的时,一般优先考虑
向前声明
,而不是作为头文件导入;
5.1 案例
class B;
class A{
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
weak_ptr<B> _ptrb;
};
class B{
public:
B() { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
weak_ptr<A> _ptra;
};
int main() {
shared_ptr<A> pa(new A());
shared_ptr<B> pb(new B());
// 此处互相引用,而类内使用weak_ptr阔以解决无法释放的问题;
pa->_ptrb = pb;
pb->_ptra = pa;
}