本质:
是将指针分装成类对象,并在析构的时候删除指针指向的内存空间。
主要的作用是对作用域内的动态分配内存对象进行自动施放,解决使用指针带来的内存泄漏问题。
在C++中没有垃圾回收机制,必须自己释放分配的内存,否则就会造成内存泄露。解决这个问题最有效的方法是使用智能指针(smart pointer)。
智能指针是存储指向动态分配(堆)对象指针的类,用于生存期的控制,能够确保在离开指针所在作用域时,自动地销毁动态分配的对象,防止内存泄露。
智能指针的核心实现技术是引用计数,每使用它一次,内部引用计数加1,每析构一次内部的引用计数减1,减为0时,删除所指向的堆内存。
1. auto_ptr
特点:这种类型的指针在进行拷贝构造的时候,会将自己的值赋值給另外一个指针,然后将自己指针的内容进行清空。
using namespace std;
void autoPtrTest()
{
auto_ptr<int> ptr(new int(100));
cout <<"addr:" << &ptr << " value:" << * ptr << endl;
*ptr = 20;
cout << "addr:" << &ptr << " value:" << *ptr << endl;
auto_ptr<int> ptr3(ptr);
cout << "addr:" << &ptr3 << " value:" << *ptr3 << endl;
// cout << *ptr << endl; // 此时ptr已经被清空
//存储指针的地址已发生变化(&ptr和&ptr3),指针本身(ptr)和指针里存储的值(20)也没有发生变化。
}
当auto_ptr<int> ptr3(ptr)时,调用auto_ptr的构造函数,和release函数,详见memory文件
auto_ptr(auto_ptr& _Right) noexcept : _Myptr(_Right.release()) {}
_Ty* release() noexcept {
_Ty* _Tmp = _Myptr;
_Myptr = nullptr;
return _Tmp;
}
note:
- 两个auto_ptr不能同时拥有同一个对象,auto_ptr被拷贝或被赋值后,失去对原指针的管理.这种情况被称为指针所有权传递(将自己的值赋值給另外一个指针,然后将自己指针的内容进行清空)
- auto_ptr不能管理数组指针。
- auto_ptr不能作为容器对象,因为STL容器中的元素经常要支持拷贝,赋值等操作。
2. unique_ptr
特点:无法进行复制和赋值(独享被管理对象指针所有权的智能指针,如果你拷贝一个unique_ptr,那么拷贝结束后,这两个unique_ptr都会指向相同的资源,它们都认为自己拥有这块资源,当指针析构时,都会企图释放,它所拥有的资源也被销毁。
复制: unique_ptr ptr1(ptr2)
赋值:ptr1 = ptr2
)
unique_ptr具有->
和*
运算符重载符,因此它可以像普通指针一样使用
默认情况下,资源的析构是伴随着调用unique_ptr内部的原始指针的delete操作的。
独享被管理对象指针所有权的智能指针。unique_ptr对象包装一个原始指针,并负责其生命周期。当该对象被销毁时,会在其析构函数中删除关联的原始指针。
裸指针直接初始化,但不能通过隐式转换来构造,因为unique_ptr构造函数被声明为explicit;
允许移动构造,但不允许拷贝构造,因为unique_ptr是个只移动类型;
通过make_unique构造,但这是C++14才支持的语法。需要注意的是:make_unique不支持添加删除器,或者初始化列表。
unique_ptr<Frame> f(new Frame()); // 裸指针直接初始化
unique_ptr<Frame> f1 = new Frame(); // Error,explicit禁止隐式初始化
unique_ptr<Frame> f2(f); // Error,禁止拷贝构造函数
unique_ptr<Frame> f3 = f; // Error,禁止拷贝构造函数
f1 = f; // Error,禁止copy赋值运算符重载
unique_ptr<Frame> f4(std::move(new Frame())); // 移动构造函数
unique_ptr<Frame> f5 = std::move(new Frame()); // Error,explicit禁止隐式初始化
unique_ptr<Frame> f6(std::move(f4)); // 移动构造函数
unique_ptr<Frame> f7 = std::move(f6); // move赋值运算符重载
unique_ptr<Frame[]> f8(new Frame[10]()); // 指向数组
auto f9 = std::make_unique<Frame>(); // std::make_unique来创建,C++14后支持
class Frame {};
void fun(std::unique_ptr<Frame> f) {}
std::unique_ptr<Frame> getfun() {
return std::unique_ptr<Frame>(new Frame()); // 右值,被移动构造
// 就算不是右值,也会被编译器RVO优化掉
}
int main()
{
std::unique_ptr<Frame> f1(new Frame());
Frame* f2 = new Frame();
fun(f1); // Error,禁止拷贝构造函数
fun(f2); // Error,explit禁止隐式转换
fun(std::move(f1)); // 移动构造函数
std::unique_ptr<Frame> f3 = getfun(); // 移动构造函数
return 0;
}
u.get():返回unique_ptr中保存的裸指针;
u.reset(…):重置unique_ptr;
u.release():放弃对指针的控制权,返回裸指针,并将unique_ptr自身置空。需要注意,此函数放弃了控制权但不会释放内存,如果不获取返回值,就丢失了指针,造成内存泄露;
u.swap(…):交换两个unique_ptr所指向的对象。
与auto_ptr相比unique_ptr有如下特点:
unique_ptr是一个独享所有权的智能指针,无法进行复制构造、copy赋值操作,只能进行移动操作。无法使两个unique_ptr指向同一个对象;
unique_ptr智能指向一个对象,如果当它指向其他对象时,之前所指向的对象会被摧毁;
unique_ptr对象会在它们自身被销毁时使用删除器自动删除它们管理的对象;
unique_ptr支持创建数组对象方法。
unique_ptr的构造函数被声明为explicit,禁止隐式类型转换的行为。可避免将一个普通指针传递给形参为智能指针的函数。假设,如果允许将裸指针传给void foo(std::unique_ptr<T>)函数,则在函数结束后会因形参超出作用域,裸指针将被delete的误操作;
unique_ptr的拷贝构造和拷贝赋值均被声明为delete。因此无法实施拷贝和赋值操作,但可以移动构造和移动赋值;
删除器是unique_ptr类型的一部分。默认为std::default_delete,内部是通过调用delete来实现;
unique_ptr可以指向数组,并重载了operator []运算符。
额外需要注意:尽管unique_ptr禁止了拷贝构造和拷贝赋值,但是,nullptr是可以用来赋值的
u = nullptr; //释放u所指向的对象,将u置为空
u.reset(nullptr); // u置为空
3. shared_ptr
shared_ptr相比较unique_ptr可以进行赋值和复制(可以进行复制和赋值而不需要担心二次释放的问题的原因是,在该类型的指针中由一个计数器,每次赋值和复制的时候都会进行计数,只有当该计数器值为 0 的时候才会释放对应指向的空间)
shared_ptr 循环引用问题
什么是循环引用,两个对象相互使用shared_ptr指向对方。造成的后果是:内存泄漏
共享智能指针是指多个智能指针可以同时管理同一块有效的内存,共享智能指针shared_ptr 是一个模板类,如果要进行初始化有三种方式:
- 构造函数
- std::make_shared辅助函数
- reset方法
共享智能指针对象初始化完毕之后就指向了要管理的那块堆内存,如果想要查看当前有多少个智能指针同时管理着这块内存可以使用共享智能指针提供的一个成员函数use_count
对应基础数据类型来说,通过操作智能指针和操作智能指针管理的内存效果是一样的,可以直接完成数据的读写。但是如果共享智能指针管理的是一个对象,那么就需要取出原始内存的地址再操作,可以调用共享智能指针类提供的get()方法得到原始地址。
当智能指针管理的内存对应的引用计数变为0的时候,这块内存就会被智能指针析构掉了。另外,我们在初始化智能指针的时候也可以自己指定删除动作,这个删除操作对应的函数被称之为删除器,这个删除器函数本质是一个回调函数,我们只需要进行实现,其调用是由智能指针完成的。
4. unique_ptr
std::unique_ptr是一个独占型的智能指针,它不允许其他的智能指针共享其内部的指针,可以通过它的构造函数初始化一个独占智能指针对象,但是不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr。
unique_ptr指定删除器和shared_ptr指定删除器是有区别的,unique_ptr指定删除器的时候需要确定删除器的类型,所以不能像shared_ptr那样直接指定删除器
5.weak_ptr
弱引用智能指针std::weak_ptr可以看做是shared_ptr的助手,它不管理shared_ptr内部的指针。std::weak_ptr没有重载操作符*和->,因为它不共享指针,不能操作资源,所以它的构造不会增加引用计数,析构也不会减少引用计数,它的主要作用就是作为一个旁观者监视shared_ptr中管理的资源是否存在。
wp1;构造了一个空weak_ptr对象
weak_ptr<int> wp2(wp1); 通过一个空weak_ptr对象构造了另一个空weak_ptr对象
weak_ptr<int> wp3(sp); 通过一个shared_ptr对象构造了一个可用的weak_ptr实例对象
wp4 = sp; 通过一个shared_ptr对象构造了一个可用的weak_ptr实例对象(这是一个隐式类型转换)
wp5 = wp3; 通过一个weak_ptr对象构造了一个可用的weak_ptr实例对象
通过调用std::weak_ptr类提供的expired()方法来判断观测的资源是否已经被释放
通过调用std::weak_ptr类提供的lock()方法来获取管理所监测资源的shared_ptr对象
通过调用std::weak_ptr类提供的reset()方法来清空对象,使其不监测任何资源
- 返回管理this的shared_ptr
- 解决循环引用问题
#include <iostream>
#include <memory>
class Woman;
class Man {
private:
//std::weak_ptr<Woman> _wife;
std::shared_ptr<Woman> _wife;
public:
void setWife(std::shared_ptr<Woman> woman) {
_wife = woman;
}
void doSomthing() {
if(_wife.lock()){}
}
~Man() {
std::cout << "kill man\n";
}
};
class Woman {
private:
//std::weak_ptr<Man> _husband;
std::shared_ptr<Man> _husband;
public:
void setHusband(std::shared_ptr<Man> man) {
_husband = man;
}
~Woman() {
std::cout <<"kill woman\n";
}
};
int main(int argc, char** argv) {
std::shared_ptr<Man> m(new Man());
std::shared_ptr<Woman> w(new Woman());
if(m && w) {
m->setWife(w);
w->setHusband(m);
}
return 0;
}
1)如果程序要使用多个指向同一个对象的指针,应选择 shared_ptr。这样的情况包括:
将指针作为参数或者函数的返回值进行传递的话,应该使用 shared_ptr;
两个对象都包含指向第三个对象的指针,此时应该使用 shared_ptr 来管理第三个对象;
STL 容器包含指针。很多 STL 算法都支持复制和赋值操作,这些操作可用于 shared_ptr,但不能用于 unique_ptr(编译器发出 warning)和 auto_ptr(行为不确定)。如果你的编译器没有提供 shared_ptr,可使用 Boost 库提供的 shared_ptr。
(2)如果程序不需要多个指向同一个对象的指针,则可使用 unique_ptr。如果函数使用 new 分配内存,并返还指向该内存的指针,将其返回类型声明为 unique_ptr 是不错的选择。这样,所有权转让给接受返回值的 unique_ptr,而该智能指针将负责调用 delete
(3)虽然说在满足 unique_ptr 要求的条件时,使用 auto_ptr 也可以完成对内存资源的管理,但是因为 auto_ ptr 不够安全,不提倡使用,即任何情况下都不应该使用 auto_ptr。
(4)为了解决 shared_ptr 的循环引用问题,我们可以祭出 weak_ptr。
(5)在局部作用域(例如函数内部或类内部),且不需要将指针作为参数或返回值进行传递的情况下,如果对性能要求严格,使用 scoped_ptr 的开销较 shared_ptr 会小一些。