概念
对程序员来说指针是难点,而且经常出现申请的空间在函数结束时忘记释放,造成内存泄漏。然而智能指针可以很大程度上地避免这个问题。智能指针主要用于管理在堆上分配的内存,它是一个类,当超出类的实例对象的作用域时,会自动调用对象的析构函数,析构函数会自动释放资源,从而防止内存泄漏。
auto_ptr
auto_ptr是C++98提出的方案,C++11已经抛弃,它采用所有权模式。
【指针赋值】
auto_ptr<string> p1(new string("auto ptr"));
auto_ptr<string> p2;
p2 = p1; //auto_ptr不会报错.
cout << *p2 << endl;//输出auto ptr
cout << *p1 << endl;//报错
由于p2剥夺了p1的所有权,拥有p1的内存块;而p1此时是野指针,当程序运行时访问p1将会报错。所以auto_ptr的缺点是:存在潜在的内存崩溃问题!
【保存数组】
class CPeople
{
public:
CPeople() { cout << "People constructor 1" << endl; }
CPeople(int n) : num(n) { cout << "People constructor 2" << endl; }
~CPeople() { cout << "People distructor" << endl; }
public:
int num;
};
这里实例化多个people并保存到数组中:
std::vector<std::auto_ptr<CPeople>> peoples;
peoples.push_back(std::auto_ptr<CPeople>(new CPeople(0)));
peoples.push_back(std::auto_ptr<CPeople>(new CPeople(1)));
peoples.push_back(std::auto_ptr<CPeople>(new CPeople(2)));
peoples.push_back(std::auto_ptr<CPeople>(new CPeople(3)));
std::auto_ptr<CPeople> one = peoples[1];
std::cout << peoples[1]->num << std::endl;//崩溃
代码运行时崩溃,问题就出在:
std::auto_ptr<CPeople> one = peoples[1];
这行代码会将peoples[1]中的指针所有权转移了!即该变量中的指针已经为NULL了。后续对解引用是不正确的。
【函数传参】
void do_somthing(std::auto_ptr<CPeople> people)
{
// 该函数内不对people变量执行各种隐式/显示的所有权转移和释放
...
}
std::auto_ptr<CPeople> people(new CPeople(3));
do_something(people);
...
std::cout << people->num << std::endl;
这里对auto_ptr的使用与原始指针的使用完全相同,但是该代码还是会崩溃!问题就在于执行do_something()函数时,传递参数导致原变量的指针所有权转移了,即people变量实际已经变为NULL了!原因在于std::auto_ptr支持拷贝构造,为了确保指针所有者唯一,这里转移了所有权!
unique_ptr
unique_ptr实现独占式拥有或严格拥有的概念,保证同一时间内只有一个智能指针可以指向内存块,这样避免出现野指针的现象。
unique_ptr<CPeople> p1 = make_unique<CPeople>(1);
unique_ptr<CPeople> p2;
p2 = p1; //报错,无法引用
编译器认为p2 = p1非法,避免了p1不再指向有效数据的问题。尝试复制p1时会编译期出错,而auto_ptr能通过编译期从而在运行期埋下出错的隐患。因此,unique_ptr比auto_ptr更安全。
如果创建一个unique_ptr临时右值,可以将该右值赋值给另一个指针:
unique_ptr<string> pu1(new string("hello world"));
unique_ptr<string> pu2;
pu2 = pu1; // #1 不允许
unique_ptr<string> pu3;
pu3 = unique_ptr<string>(new string("You")); // #2 允许
C++有一个标准库函数std::move(),让你能够将一个unique_ptr赋给另一个。尽管转移所有权后还是有可能出现原有指针调用崩溃的情况,但是这个语法能强调你是在转移所有权,让你清晰地知道自己在做什么,从而不乱调用原有指针。
unique_ptr<CPeople> p1 = make_unique<CPeople>(1);
unique_ptr<CPeople> p2;
p2 = std::move(p1);
cout << p1->num << endl; //报错
shared_ptr
C++11中最常用的智能指针类型为shared_ptr,它实现共享式拥有概念。多个智能指针可以指向相同内存块,该内存块和其相关资源会在“最后一个引用被销毁”时候释放。shared_ptr采用引用计数的方法,记录当前内存资源被智能指针引用的次数。引用计数的内存在堆上分配;当新增一个引用时,引用计数加1;当不引用时,引用计数减1;当引用计数为0时,智能指针会自动释放引用的内存资源。使用shared_ptr创建的指针不能采用普通指针直接赋值的方式,可以通过make_shared函数或者通过构造函数传入普通指针,因为shared_ptr是一个类。
shared_ptr是为了解决 auto_ptr在对象所有权上的局限性(auto_ptr是独占的), 在使用引用计数的机制上提供了可以共享所有权的智能指针。成员函数为:
- use_count返回引用计数的个数。
- unique返回是否是独占所有权(use_count为1)。
- swap交换两个shared_ptr对象(即交换所拥有的对象)。
- reset放弃内部对象的所有权或拥有对象的变更, 会引起原有对象的引用计数的减少。
- get返回内部对象(指针), 由于已经重载了()方法, 因此和直接使用对象是一样的。
【应用实例】
string* s1 = new string("s1");
shared_ptr<string> ps1(s1);
shared_ptr<string> ps2;
ps2 = ps1;
cout << ps1.use_count() << endl; //2
cout << ps2.use_count() << endl; //2
cout << ps1.unique() << endl; //0
string* s3 = new string("s3");
shared_ptr<string> ps3(s3);
cout << ps1.get() << endl; //00EDC618
cout << ps3.get() << endl; //00EDC810
swap(ps1, ps3); //交换所拥有的对象
cout << ps1.get() << endl; //00EDC810
cout << ps3.get() << endl; //00EDC618
cout << ps1.use_count() << endl; //1
cout << ps2.use_count() << endl; //2
ps2 = ps1;
cout << ps1.use_count() << endl; //2
cout << ps2.use_count() << endl; //2
ps1.reset(); //放弃ps1的拥有权,引用计数的减少
cout << ps1.use_count() << endl; //0
cout << ps2.use_count() << endl; //1
shared_ptr可以被安全地共享——shared_ptr是一个“全功能”的类,有着正常的拷贝、赋值语义,也可以进行shared_ptr间的比较,是“最智能”的智能指针。
shared_ptr有多种形式的构造函数,应用于各种可能的情形:
- 无参的shared_ptr()创建一个持有空指针的shared_ptr。
- shared_ptr(Y *p)获得指向类型T的指针p的管理权,同时引用计数值为1。这个构造函数要求Y类型必须能够转换为T类型。
- shared_ptr(shared_ptr const & r)从另外一个shared_ptr获得指针的管理权,同时引用计数加1,结果是两个shared_ptr共享一个指针的管理权。
- shared_ptr(std::auto_ptr<Y> & r)从一个auto_ptr获得指针的管理权,引用计数值为1,同时auto_ptr自动失去管理权。
- operator=赋值操作符可以从另外一个shared_ptr或auto_ptr获得指针的管理权,其行为同构造函数。
- shared_ptr( Y *p, D d)行为类似shared_ptr(Y * p),但使用参数d指定了析构时的定制删除器,而不是简单的delete。
shared_ptr的reset()函数的作用是将引用计数减1,停止对指针的共享,除非引用计数为0,否则不会发生删除操作。
shared_ptr有两个专门的函数检查引用计数。unique()在shared_ptr是指针的唯一拥有者时返回true。use_count()返回当前指针的引用计数。use_count( )应该仅仅用于测试或者调试,它不能提供高效率的操作,而且有的时候可能是不可用的(极少数情形)。而unique()则是可靠的,任何时候都可用,而且比use_count() == 1速度更快。
shared_ptr还支持比较运算符,可以测试两个shared_ptr的相等或者不等,比较基于内部保存的指针,相当于a.get() == b.get()。
shared_ptr还可以使用operator<比较大小,同样基于内部保存的指针,但不提供除operator<以外的比较操作符,这使得shared_ptr可以被用于标准关联容器(set和 map)
shared_ptr还支持流输出操作符operator<<,输出内部的指针值,方便调试。
weak_ptr
虽然share_ptr有很多优点,但还是有内存泄露的情况,当两个对象相互使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏。
【应用实例1】
#include <iostream>
using namespace std;
class TESTB;
class TESTA
{
public:
TESTA() { cout << "Create TESTA" << endl; }
~TESTA() { cout << "Delete TESTA" << endl; }
shared_ptr<TESTB> pTB;
};
class TESTB
{
public:
TESTB() { cout << "Create TESTB" << endl; }
~TESTB() { cout << "Delete TESTB" << endl; }
shared_ptr<TESTA> pTA;
};
int main()
{
{
shared_ptr<TESTA> pa = make_shared<TESTA>();
shared_ptr<TESTB> pb = make_shared<TESTB>();
cout << pa.use_count() << endl;//1
cout << pb.use_count() << endl;//1
pa->pTB = pb;
pb->pTA = pa;
cout << pa.use_count() << endl;//2
cout << pb.use_count() << endl;//2
}
return 0;
}
运行结果:
【应用实例2】
#include <iostream>
using namespace std;
class TESTB;
class TESTA
{
public:
TESTA() { cout << "Create TESTA" << endl; }
~TESTA() { cout << "Delete TESTA" << endl; }
shared_ptr<TESTB> pTB;
};
class TESTB
{
public:
TESTB() { cout << "Create TESTB" << endl; }
~TESTB() { cout << "Delete TESTB" << endl; }
shared_ptr<TESTA> pTA;
};
int main()
{
{
shared_ptr<TESTA> pa = make_shared<TESTA>();
shared_ptr<TESTB> pb = make_shared<TESTB>();
cout << pa.use_count() << endl;//1
cout << pb.use_count() << endl;//1
//pa->pTB = pb;
//pb->pTA = pa;
//cout << pa.use_count() << endl;//2
//cout << pb.use_count() << endl;//2
}
return 0;
}
运行结果:
从两个案例可以看到函数中pa,pb之间互相引用,两个资源的引用计数为2,当要跳出函数时,智能指针pa,pb析构时两个资源引用计数会减1,但是两者引用计数还是为1,导致跳出函数时资源没有被释放(TESTA、TESTB的析构函数没有被调用)运行结果没有输出析构函数的内容,造成内存泄露。
weak_ptr是为了配合shared_ptr而引入的一种智能指针,因为它不具有普通指针的行为,没有重载operator*和->,它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况。weak_ptr可以从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权。但weak_ptr没有共享资源,它的构造不会引起指针引用计数的增加。使用weak_ptr的成员函数use_count()可以观测资源的引用计数,另一个成员函数expired()的功能等价于use_count()==0,但更快,表示被观测的资源(也就是shared_ptr的管理的资源)已经不复存在。weak_ptr可以使用一个非常重要的成员函数lock()从被观测的shared_ptr获得一个可用的shared_ptr对象, 从而操作资源。但当expired()==true的时候,lock()函数将返回一个存储空指针的shared_ptr。
【应用实例】
#include <iostream>
int main()
{
{
std::shared_ptr<int> sh_ptr = std::make_shared<int>(10);
std::cout << sh_ptr.use_count() << std::endl;
std::weak_ptr<int> wp(sh_ptr);
std::cout << wp.use_count() << std::endl;//1
if (!wp.expired())
{
std::shared_ptr<int> sh_ptr2 = wp.lock(); //get another shared_ptr
*sh_ptr = 100;
std::cout << wp.use_count() << std::endl;//2
}
}
}