文章目录
前言
智能指针不是指针,是一个管理指针的类,用来存储指向动态分配对象的指针,负责自动释放动态分配的对象,防止堆内存泄漏和空悬指针等等问题。
动态分配的资源,交给一个类对象去管理,当类对象声明周期结束时,自动调用析构函数释放资源。
智能指针的析构为当智能指针实例对象脱离作用域析构时,若其所指向的内存地址,没有其他在指向,则释放内存空间
如这段代码,vector保存了该内存地址,则局部变量智能指针析构后,其内存地址不会被释放:
std::vector<std::unique_ptr<Thread>> threads_;//线程列表
//开启线程池
void ThreadPool::start(int initThreadSize)
{
//记录初始线程个数
initThreadSize_ = initThreadSize;
//创建线程对象
for (int i = 0; i < initThreadSize_; i++)
{
//创建thread线程对象的时候,把线程函数给到thread线程对象
auto ptr = std::make_unique<Thread>(new Thread(std::bind(&ThreadPool::threadFunc, this)));
threads_.emplace_back(std::move(ptr));//unique_ptr不允许普通的拷贝构造与赋值
}
//启动所有线程 std::vector<Thread*> threads_;
for (int i = 0; i < initThreadSize_; i++)
{
threads_[i]->start();
}
}
智能指针的历史历程:
C++ 98 中产生了第一个智能指针auto_ptr。
C++boost给出了更加实用的scoped_ptr(防止拷贝) 和 shared_ptr(引进引用计数) 和 weak_ptr。
C++ 11 引入了unquie_ptr 和 shared_ptr 和 weak_ptr .需要注意的是,unique_ptr对应的是boost中的scoped_ptr。并且这些智能指针的实现是参照boost中的实现的。
先看一下智能指针的使用场景
{
shared_ptr<Connection> sp_conn = ConnectionPool::getConnectionPool()->getConnection();
}
// 给外部提供接口,从连接池中获取一个可用的空闲连接
shared_ptr<Connection> ConnectionPool::getConnection()
{
unique_lock<mutex> lock(_queueMutex);
while (_connectionQue.empty())
{
// sleep
if (cv_status::timeout == cv.wait_for(lock, chrono::milliseconds(_connectionTimeout)))
{
if (_connectionQue.empty())
{
LOG_INFO << "获取空闲连接超时了...获取连接失败!";
return nullptr;
}
}
}
/*
shared_ptr智能指针析构时,会把connection资源直接delete掉,相当于
调用connection的析构函数,connection就被close掉了。
这里需要自定义shared_ptr的释放资源的方式,把connection直接归还到queue当中
*/
shared_ptr<Connection> sp(_connectionQue.front(),
[&](Connection *pcon) {
// 这里是在服务器应用线程中调用的,所以一定要考虑队列的线程安全操作
unique_lock<mutex> lock(_queueMutex);
pcon->refreshAliveTime(); // 刷新一下开始空闲的起始时间
_connectionQue.push(pcon);
});
_connectionQue.pop();
cv.notify_all(); // 消费完连接以后,通知生产者线程检查一下,如果队列为空了,赶紧生产连接
return sp;
}
在一个{}作用域中创建智能指针,那么在离开这个作用域时,智能指针会自动析构释放资源,在linux开发中应该尽量多用智能指针来规避内存问题。
1.为什么需要智能指针
在使用指针时容易引发的问题之一就是内存泄漏。
内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。
内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对 该段内存的控制,因而造成了内存的浪费。
内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现 内存泄漏会导致响应越来越慢,最终卡死。
如何防止内存泄漏呢
代码如下
void fxx()
{
int* p1 = new int[10];
int* p2 = new int[20];
int* p3 = new int[30];
//...
delete[] p1;
delete[] p2;
delete[] p3;
}
如果指针p2或者p3开辟空间new错误,这里就会导致后面的delete不会被执行,这就导致了指针p1的内存泄漏。这里我们可以用异常来解决,但是很难看:
void fxx()
{
int* p1 = new int[10];
int* p2, *p3;
try
{
p2 = new int[20];
try {
p3 = new int[30];
}
catch (...)
{
delete[] p1;
delete[] p2;
throw;
}
}
catch (...)
{
delete[] p1;
throw;
}
//...
delete[] p1;
delete[] p2;
delete[] p3;
}
异常的重新捕获
double Division(int a, int b)
{
// 当b == 0时抛出异常
if (b == 0)
{
throw "Division by zero condition!";
}
return (double)a / (double)b;
}
void fyy() noexcept
{
int len, time;
cin >> len >> time;
cout << Division(len, time) << endl;
}
void func()
{
//这里可以看到如果发生除0错误抛出异常,下面的array数组就没有得到释放
//所以这里捕获异常但是不处理异常,异常还是交给外面处理,这里捕获了再抛出去
//就能delete array了
int* array = new int[10];
try
{
fyy();
}
catch (...)
{
//捕获异常不是为了处理异常
//是为了释放内存,然后异常再重新抛出
cout << "delete[]" << array << endl;
delete[] array;
throw;//捕到什么抛什么
}
cout << "delete[]" << array << endl;
delete[] array;
}
但是当有很多个变量要new和delete呢?就跟上面一样,会导致代码的繁琐嵌套,所以我们要用智能指针来解决。
2.智能指针的原理与使用
2.1 智能指针的原理
智能指针的基本原理是利用RAII。
RAII:RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在 对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做 法有两大好处:
- 不需要显式地释放资源。
- 采用这种方式,对象所需的资源在其生命期内始终保持有效。
template<class T>
class Smartptr
{
public:
//RAII
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(1));
Smartptr<int> sp2(new int(2));
*sp1 += 10;
Smartptr<pair<string, int>> sp3(new pair<string, int>);
sp3->first = "apple";
sp3->second = 1;
return 0;
}
2.2 智能指针的使用
代码如下(示例):
template<class T>
class Smartptr
{
public:
//RAII
Smartptr(T* ptr)
:_ptr(ptr)
{}
~Smartptr()
{
cout << "delete:" << _ptr << endl;
delete _ptr;
}
//像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
int div()
{
int a, b;
cin >> a >> b;
if (b == 0)
{
throw invalid_argument("除0错误");
}
return a / b;
}
void func()
{
Smartptr<int> sp1(new int(1));
Smartptr<int> sp2(new int(2));
*sp1 += 10;
cout << div() << endl;
}
int main()
{
try
{
func();
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
通过SmartPtr对象,无论程序是正常执行结束,还是因为某些中途原因进行返回,或者抛出异常等开始所面临的困境,只要SmartPtr对象的生命周期结束就会自动调用对应的析构函数,不会造成内存泄漏,完成资源释放。
2.3 智能指针的拷贝问题
如果我们用一个智能指针拷贝构造一个智能指针,或者用一个智能指针赋值给另一个智能指针。这样的操作都会导致程序崩溃。
void test()
{
SmartPtr<int> sp1(new int);
SmartPtr<int> sp2(sp1);//拷贝构造
SmartPtr<int> sp3(new int);
SmartPtr<int> sp4 = sp3;//赋值
}
因为对于我们的智能指针来说,将sp1拷贝给sp2操作是浅拷贝,是将两个指针的指向统一到一块空间。当sp1和sp2释放时,会导致这块空间释放两次。同样的道理,将sp3赋值给sp4的时候,也只是单纯的将指针的指向指到同一块空间,这样在析构的时候也会导致析构两次。
所以对于如何解决这个问题,智能指针分为了很多版本。
3.四种智能指针的使用与注意事项
C++中存在4种智能指针:auto_ptr,unquie_ptr,shared_ptr,weak_ptr,他们各有优缺点,以及对应的实用场景。
shared_ptr允许多个指针指向同一个变量。
unique_ptr则独占所指向的变量。
weak_ptr则指向shared_ptr所管理的变量。
智能指针 | 类型 |
---|---|
shared_ptr | 共享指针,多个共享指针可以指向同一个对象 |
weak_ptr | 该指针是对象的一个非拥有性引用,指向一个shared_ptr,主要用来避免shared_ptr环形依赖 |
unquie_ptr | 该指针独占对象的所有权,每个对象智能有一个该指针 |
auto_ptr :管理权转移,被拷贝对象把资源管理权转移给拷贝对象,导致被拷贝对象悬空。
auto_ptr是C++98的,通过管理权转移的方式解决智能指针拷贝问题,保证了一个资源只有一个对象对其进行管理,这时候一个资源就不会被多个释放
实际上auto_ptr几乎不使用,使用最多的是shared_ptr
3.1 智能指针的基础用法
3.1.1 智能指针的初始化
智能指针是基于类模板生成的,因此,要初始化一个智能指针,就必须声明指针所指向的数据类型,不然智能指针里面包含的原始指针是个空指针。
初始化方式一,在智能指针构造函数中new一个新对象。
struct C{
int a;
int b;
};
std::shared_ptr<C> p1(new C);
std::unique_ptr<int> p2(new int(40));
初始化方式二,采用make_shared函数(C++11标准)、make_unique函数(C++14标准)。
std::shared_ptr<int> p3 = std::make_shared<int>(15);
std::unique_ptr<int> p4 = std::make_unique<int>(10);
智能指针在初始化时,还可以用于指向动态分配的数组。
代码样例,创建长度为10的整型数组:
//方式一
auto Array_1 = make_unique<int[]>(10);
//方式二
std::unique_ptr<int[]> Array_2(new int[10]);
//类型+[],表示初始化指向数组的智能指针
//后面的具体用法和数组类似
Array_1[0] = 1;
Array_2[0] = 2;
注意,初始化weak_ptr需要用到shared_ptr。
代码样例:
auto sh_p = make_shared<int>(40);
weak_ptr<int> wp1(sh_p);
weak_ptr<int> wp2 = sh_p;
3.1.2 智能指针的解引用
智能指针的解引用操作与原始指针类似,可以调用"*“或”->"对智能指针进行解引用,访问分配到的堆内存地址。
但是weak_ptr不提供指针的解引用操作,即无法调用"*“或”->"获得weak_ptr所指向的变量。
#include <iostream>
#include <memory>
int main() {
struct C {
int a=1;
int b=2;
};
std::shared_ptr<C> p1(new C);
std::unique_ptr<int> p2(new int(40));
std::shared_ptr<int> p3 = std::make_shared<int>(15);
std::unique_ptr<int> p4 = std::make_unique<int>(10);
std::weak_ptr<int> p5 = p3;
std::cout << p1->a << std::endl;
std::cout << p1->b << std::endl;
std::cout << *p2 << std::endl;
std::cout << *p3 << std::endl;
std::cout << *p4 << std::endl;
std::cout << *p5.lock() << std::endl;
return 0;
}
//运行结果
1
2
40
15
10
15
3.2 unique_ptr智能指针
unique_ptr是独享的,不可以两个unique_ptr同时指向同一份资源
由于独占资源控制权,所以不支持普通拷贝,但可以转移控制权
unique_ptr是独享被管理对象指针所有权(owership)的智能指针。unique_ptr对象封装一个原始指针,并负责其生命周期。当该对象被销毁时,会在其析构函数中删除关联的原始指针。
常用的成员函数:
get(): 返回指向变量的原始指针。
reset(): 重置智能指针,使它所持有的资源为空。
swap(): 交换两个智能指针所管理的资源。
release(): 返回指向变量的原始指针,并释放所有权。
用法说明:
- reset()让unique_ptr重新指向给定的指针。如果unique_ptr不为空,它原先占有的内存资源将被释放。
- 由于一个初始化后的unique_ptr独占了它所指向的变量,因此unique_ptr不支持普通的拷贝或赋值操作。
- 虽然不能拷贝或赋值unique_ptr,但可以通过调用release()/reset()函数将指针的所有权转移给另一个unique_ptr。
unique_ptr的成员方法release,release可以返回unique_ptr的内置指针,并将unique_ptr置为空。 下上述代码将ball的内置指针转移给ball2了。同样的道理,通过reset操作, ball放弃原来的内置指针,指向new Ball()
的内置指针。 release()操作提供了返回unique_ptr的内置指针的方法,但要注意release过后unique_ptr被置空,那返回的内置指针要么手动释放,要么交给其他的智能指针管理。
unique_ptr<Ball> ball = make_unique<Ball>();//创建unique_ptr
Ball *p = ball.get();//获得裸指针
ball.reset(new Ball());//重新指向另一个Ball对象
Ball *ball2 = ball.release();//unique_ptr与资源解绑,release返回资源的裸指针,同时将unique_ptr设置为nullptr,但解绑后需要用delete释放
delete ball2;
ball = nullptr;//赋值为裸指针,也会释放的所对应的资源
//转移控制权
unique_ptr<int> up1 = make_unique<int>(100);
unique_ptr<int> up2(up1.release());//方式1:转移控制权
unique_ptr<int> up3 = std::move(up1);//方式2:转移控制权
unique最常见的使用场景,就是替代原始指针,为动态申请的资源提供异常安全保证。
objtype *p = new objtype();
p -> func();
delete p
//改为如下
unique_ptr<objtype> p(new objtype());
p -> func();
delete p
此时我们只要unique_ptr创建成功,unique_ptr对应的析构函数都能保证被调用,从而保证申请的动态资源能被释放掉。
我们可以为unique_ptr指定删除器,为unique_ptr指定删除器时要在尖括号里指定删除器类型
//p 指向一个类型为objT的对象,并使用一个类型为delT的对象释放objT对象
//它会调用一个名为fcn的delT类型对象
unique_ptr<objT, delT> p(new objT, fcn);
作为一个更具体的例子,我们这样演示,先定义一个unique_deleter
void unique_deleter(int *p)
{
cout << "this is unique deleter" << endl;
cout << "inner pointer data is " << *p << endl;
}
再基于删除器定义一个unique_ptr
void use_uniqueptr()
{
unique_ptr<int, decltype(unique_deleter) *> mp(new int(1024), unique_deleter);
}
我们在主函数调用use_uniqueptr会输出如下
this is unique deleter
inner pointer data is 1024
在本例中我们使用了decltype来指明函数指针类型。由于decltype返回一个函数类型,所以我们必须添加一个*来指出我们正在使用该类型的一个指针。
3.3 shared_ptr智能指针
我们提到的智能指针,很大程度上就是指的shared_ptr,shared_ptr也在实际应用中广泛使用。它的原理是使用引用计数实现对同一块内存的多个引用。在最后一个引用被释放时,指向的内存才释放,这也是和 unique_ptr 最大的区别。当对象的所有权需要共享(share)时,share_ptr可以进行赋值拷贝。
shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。每使用他一次,内部的引用计数加1,每析构一次,内部的引用计数减1,减为0时,删除所指向的堆内存。
常用的成员函数:
get(): 返回指向变量的原始指针。
reset(): 重置智能指针,使它所持有的资源为空。
swap(): 交换两个智能指针所管理的资源。
use_count(): 返回智能指针所指向变量的被引用数量。
unique(): 检查所指向的变量是否仅由当前shared_ptr的实例管理。
用法说明:
- shared_ptr允许多个指针指向同一块堆内存。
- shared_ptr提供了引用计数,监视当前变量正在被多少个指针实例所引用。
- 由于shared_ptr存在引用计数,仅在最后一个引用被销毁或重置时,该智能指针才会释放持有的内存资源。。
- shared_ptr可被以下函数强制转换:
const_pointer_cast()
dynamic_pointer_cast()
static_pointer_cast()
reinterpret_pointer_cast() (C++17标准引入)
std::shared_ptr<int> p4 = new int(1)
上面这种写法是错误的,因为右边得到的是一个原始指针
shared_ptr本质是一个对象,将一个指针赋值给一个对象是不行的。
void f2() {
shared_ptr<int> p = make_shared<int>(1);//创建shared_ptr
int *p2 = p.get();//获取原始指针
cout<<*p2<<endl;
}
shared_ptr使用需要注意的点
不能将一个原始指针初始化多个shared_ptr
void f2() {
int *p0 = new int(1);
shared_ptr<int> p1(p0);
shared_ptr<int> p2(p0);
cout<<*p1<<endl;
}
上面代码会报错。原因很简单,因为p1,p2都要进行析构删除,这样会造成原始指针p0被删除两次。
在构造智能指针的同时,可以指定自定义的删除方法替代shared_ptr自己的delete操作
//可以设置新的删除函数替代delete
shared_ptr<string> psstr4(new string("good luck for zack"), delfunc);
我们为psstr4指定了delfunc删除函数,这样当psstr4被释放时就会执行delfunc函数,而不是delete操作。
void delfunc(string *p)
{
if (p != nullptr)
{
delete (p);
p = nullptr;
}
cout << "self delete" << endl;
}
我们实现了自己的delfunc函数作为删除器,回收了内置指针,并且打印了删除信息。这样当psstr4执行析构时,会打印"self delete"。 推荐使用make_shared的方式构造智能指针。 如果通过内置指针初始化生成智能指针,那一定要记住不要手动回收内置指针。 当将一个shared_ptr绑定到一个普通指针时,我们就将内存的管理责任交给了这个shared_ptr。 一旦这样做了,我们就不应该再使用内置指针来访问shared_ptr所指向的内存了。 以下代码存在问题
void process(shared_ptr<int> psint)
{
cout << "psint data is " << *psint << endl;
}
int main()
{
int *p = new int(5);
process(shared_ptr<int>(p));
//危险,p已经被释放,会造成崩溃或者逻辑错误
cout << "p data is " << *p << endl;
return 0;
}
程序输出
psint data is 5
p data is 10569024
循环引用问题
shared_ptr最大的坑就是循环引用。引用网络上的一个例子:
struct Father
{
shared_ptr<Son> son_;
};
struct Son
{
shared_ptr<Father> father_;
};
int main()
{
auto father = make_shared<Father>();
auto son = make_shared<Son>();
father->son_ = son;
son->father_ = father;
return 0;
}
该部分代码会有内存泄漏问题。原因是
1.main 函数退出之前,Father 和 Son 对象的引用计数都是 2。
2.son 指针销毁,这时 Son 对象的引用计数是 1。
3.father 指针销毁,这时 Father 对象的引用计数是 1。
4.由于 Father 对象和 Son 对象的引用计数都是 1,这两个对象都不会被销毁,从而发生内存泄露。
为避免循环引用导致的内存泄露,就需要使用 weak_ptr。weak_ptr 并不拥有其指向的对象,也就是说,让 weak_ptr 指向 shared_ptr 所指向对象,对象的引用计数并不会增加。
使用 weak_ptr 就能解决前面提到的循环引用的问题,方法很简单,只要让 Son 或者 Father 包含的 shared_ptr 改成 weak_ptr 就可以了。
struct Father
{
shared_ptr<Son> son_;
};
struct Son
{
weak_ptr<Father> father_;
};
int main()
{
auto father = make_shared<Father>();
auto son = make_shared<Son>();
father->son_ = son;
son->father_ = father;
return 0;
}
1.main 函数退出前,Son 对象的引用计数是 2,而 Father 的引用计数是 1。
2.son 指针销毁,Son 对象的引用计数变成 1。
3.father 指针销毁,Father 对象的引用计数变成 0,导致 Father 对象析构,Father 对象的析构会导致它包含的 son_ 指针被销毁,这时 Son 对象的引用计数变成 0,所以 Son 对象也会被析构。
3.4 weak_ptr智能指针
week_ptr本身依赖shared_ptr存在,存储一个资源的引用但不能修改,只能告知资源是否存在
weak_ptr是一种不控制所指向对象生存期的智能指针,它指向由一个shared_ptr管理的对象。 将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。 一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。 即使有weak_ptr指向对象,对象也还是会被释放,因此,weak_ptr的名字抓住了这种智能指针“弱”共享对象的特点。
常用的成员函数:
reset(): 重置智能指针,使它所持有的资源为空。
swap(): 交换两个智能指针所管理的资源。
expired(): 检查weak_ptr所指向的资源是否有效,返回true的时候,垃圾回收进程就会清除该指针所指向的内存资源。
use_count(): 返回智能指针所指向shared_ptr的数量。
lock(): 获取weak_ptr所指向的shared_ptr实例。
用法说明:
- weak_ptr不占有内存资源,但是可以指向由shared_ptr管理的内存资源。
- 当weak_ptr指向shared_ptr时,是弱共享shared_ptr,并不会使shared_ptr的引用计数增加。
- weak_ptr的出现可以帮助开发者解决智能指针使用期间发生的"循环引用"问题。
cpp.lock()
,返回shared_ptr地址指针,若对象已被释放,则返回nullptr
weak_ptr同样包括reset(),use_count()等方法。 与shared_ptr不同的是,weak_ptr提供expired()方法,该方法在use_count为0时返回true, 否则返回false。所以可以通过expired方法去判断weak_ptr的内置指针是否被释放。 weak_ptr通过lock()方法返回一个shared_ptr,shared_ptr内置指针指向的空间和weak_ptr内置指针指向相同。由于weak_ptr的弱共享特点,其内置指针可能被回收,所以当expired为true时, lock()返回一个空的shared_ptr,否则返回一个shared_ptr,该shared_ptr的内置指针与weak_ptr的内置指针指向相同。
void test_weak_ptr() {
weak_ptr<int> wp;
shared_ptr<int> sp = make_shared<int>(100);
wp = sp;
auto resource = wp.lock();//wp.lock()返回指针,若被释放则返回nullptr
if (resource) {
cout << "Number is" << *resource << endl;
} else {
cout << "wp is expired" << endl;
}
}
4. 智能指针的析构操作
智能指针与异常处理
代码发生异常的时候,中间退出,那么对于new出来的对象如果没有显式释放的话就造成了内存泄露。可以用智能指针避免这一现象。
void f()
{
shared_ptr sp(new int(42));
//发生了异常,且未在f中被捕获,程序退出
}//函数结束时,sp会被析构,对象的引用计数减1变为0,自动释放对象所占用的内存
使用自己的析构操作
默认情况下,shared_ptr
认为指向动态内存,且shared_ptr
释放时会调用它管理的指针的delete操作。
但如果shared_ptr
管理的是自定义类型,且该类型没有良好设计的构造函数时,那么使用shared_ptr
依然会造成内存泄露,这种情况下可以定义一个其他的函数来代替delete操作。
void MyDelete
{
//自定义的析构函数
}
void f()
{
myType* t = new myType();
shared_ptr p(t,MyDelete);
}//函数结束或者发生异常时,会析构p,此时会调用MyDelete函数释放内存
如果智能指针管理的不是new出来的对象,那么需要自己定义一个删除器。
void MyDelete
{
//自定义操作
}
void f(input& d)
{
myType t = myType(d);//非new出来的对象
shared_ptr p(&t,MyDelete);
}//函数结束或者发生异常时,会析构p,此时会调用MyDelete函数进行指定的操作
相应的,也可以直接利用lambda函数来自定义智能指针的析构
/*
shared_ptr智能指针析构时,会把connection资源直接delete掉,相当于
调用connection的析构函数,connection就被close掉了。
这里需要自定义shared_ptr的释放资源的方式,把connection直接归还到queue当中
*/
shared_ptr<Connection> sp(_connectionQue.front(),
[&](Connection *pcon) {
// 这里是在服务器应用线程中调用的,所以一定要考虑队列的线程安全操作
unique_lock<mutex> lock(_queueMutex);
pcon->refreshAliveTime(); // 刷新一下开始空闲的起始时间
_connectionQue.push(pcon);
});
5. 总结
我们该如何选择智能指针:
如果程序要使用多个指向同一个对象的指针,应选择 shared_ptr。这样的情况包括
1.有一个指针数组,并使用一些辅助指针来标示特定的元素,如最大的元素和最小的元素;
2.两个对象包含都指向第三个对象的指针;
3.STL 容器包含指针。很多 STL 算法都支持复制和赋值操作,这些操作可用于 shared_ptr,但不能用于 unique_ptr(编译器发出 warning)和 auto_ptr(行为不确定)。如果你的编译器没有提供 shared_ptr,可使用 Boost 库提供的 shared_ptr。
如果程序不需要多个指向同一个对象的指针,则可使用 unique_ptr。如果函数使用 new 分配内存,并返还指向该内存的指针,将其返回类型声明为 unique_ptr 是不错的选择。这样,所有权转让给接受返回值的 unique_ptr,而该智能指针将负责调用 delete。