目录
介绍
在C++中,动态内存的管理是通过new和delete运算符来完成的,动态内存管理容易出现两个问题:一个是忘记释放内存,会导致内存泄露;一个是在还有指针引用的情况下就释放内存,会导致产生引用非法内存的指针。
而智能指针就是用来帮我们管理动态分配的内存,能够避免内存泄露。智能指针本质上是一个类模板,通过重载运算符*、->,使智能指针能够和普通指针一样使用。智能指针不是指针,是一个管理指针的类,用来存储指向动态分配对象的指针,负责自动释放动态分配的对象,防止堆内存泄漏;动态分配的资源,交给一个类对象去管理,当类对象声明周期结束时,自动调用析构函数释放资源。
智能指针都定义在memory头文件中。
分类
智能指针不是线程安全的,使用方法:get()、swap()、unique()、use_count()、reset()、release()、make_shared(模板方法)
unique_ptr在默认情况下和裸指针的大小是一样的,不会造成额外的内存开销;shared_ptr是裸指针的两倍,因为除了要管理一个裸指针外,还要维护一个引用计数。
(1)auto_ptr
已经被弃用了,被unique_ptr所取代。
被弃用原因:
(1)复制或者赋值都会改变资源的所有权。
int main()
{
auto_ptr<string> p(new string("11"));
auto_ptr<string> p1(new string("22"));
cout << "p:" << p.get() << endl;
cout << "p1:" << p1.get() << endl;
// p1赋值给p后,p会先将自己原先托管的指针释放掉,然后接收p1所托管的指针,
// p1所托管的指针置空,也就是p托管了p1托管的指针,而p1放弃了托管。
p = p1;
cout << "赋值后 p:" << p.get() << endl;
cout << "赋值后 p1:" << p1.get() << endl;
}
(2)在STL容器中使用会有重大风险,因为容器内的元素必须支持可复制和可赋值。
int main()
{
vector<auto_ptr<string>> vec;
auto_ptr<string> p(new string("11"));
auto_ptr<string> p1(new string("22"));
vec.push_back(move(p));
vec.push_back(move(p1));
cout << "vec[0]: " << *vec[0] << endl;
cout << "vec[1]: " << *vec[1] << endl;
// 如果进行赋值,vec[1]的元素就变成空值,会出现访问越界问题
vec[0] = vec[1];
cout << "赋值后:vec[0]: " << *vec[0] << endl;
cout << "赋值后:vec[1]: " << *vec[1] << endl;
}
(3)不支持对象数组的内存管理
//不能这样定义
auto_ptr<int[]> array(new int[2]);
(2)unique_ptr
unique_ptr将拷贝构造函数和赋值重载函数都禁用掉了,所以不能进行拷贝和复制。C++11用unique_ptr取代了auto_ptr,两者用法几乎一样。
unique_ptr特性:
(1)排他所有权模式:两个指针不能指向同一个资源。
(2)无法进行左值赋值构造和左值赋值操作,但允许临时右值赋值构造和赋值。
int main()
{
unique_ptr<int> p(new int(1));// unique_ptr<int> p1 = make_unique<int>(); 这两种方式是一样的
unique_ptr<int> p1(new int(2));
cout << "P:" << p.get() << endl;
cout << "P1:" << p1.get() << endl;
p = p1;//错误,unique_ptr不能进行拷贝
unique_ptr<int> p2(p); //错误,unique_ptr不能进行拷贝
p = move(p1);//使用move将左值转成右值就可以进行赋值了,效果和auto_ptr一样
cout << "赋值后 P:" << p.get() << endl;
cout << "赋值后 P1:" << p1.get() << endl;
system("pause");
}
(3)在容器中保存指针是安全的。
int main()
{
//可以这样定义,会自动调用delete[]函数释放内存
unique_ptr<int[]> array(new int[2]);
vector<unique_ptr<string>> vec;
unique_ptr<string> p(new string("11"));
unique_ptr<string> p1(new string("22"));
vec.push_back(move(p));
vec.push_back(move(p1));
cout << "vec[0]: " << *vec[0] << endl;
cout << "vec[1]: " << *vec[1] << endl;
// 如果进行赋值,vec[1]的元素就变成空值,会出现访问越界问题
vec[0] = vec[1];//错误,不允许直接赋值
vec[0] = move(vec[1]); //如果一定要赋值,使用move修饰
cout << "赋值后:vec[0]: " << *vec[0] << endl;
cout << "赋值后:vec[1]: " << *vec[1] << endl;
}
(3)shared_ptr
shared_ptr允许多个智能指针可以指向同一块资源,并且能够保证共享的资源只会被释放一次。shared_ptr采用的是引用计数原理来实现多个shared_ptr对象之间共享资源,当有一个shared_ptr对象引用这个资源时,引用计数加1,当一个shared_ptr对象被销毁时,会调用析构函数,引用计数减1,当引用计数为0时,这个资源就会被自动释放掉。
实例:输出结果都是1
int main()
{
shared_ptr<int> p(new int(1));
cout << *p << endl;
shared_ptr<int> p1 = p;
cout << *p1 << endl;
shared_ptr<int> p2 = make_shared<int>(2);
p2 = p;
cout << *p2 << endl;
system("pause");
}
shared_ptr会出现循环引用的问题:
- use_count(): 返回智能指针对象的引用计数。
实例:输出结果都是2;
struct A
{
shared_ptr<A> a;
shared_ptr<A> b;
};
int main()
{
shared_ptr<A> aa(new A);
shared_ptr<A> bb(new A);
aa->b = bb;
bb->a = aa;
// use_count(): 返回智能指针对象的引用计数。
cout << "aa的引用计数" << aa.use_count() << endl;
cout << "bb的引用计数" << bb.use_count() << endl;
system("pause");
}
原因:
(1)这是因为当创建出aa和bb时,引用计数都是1。
(2)当aa的b指向bb所指向的资源时,bb的引用计数就+1,bb的a指向aa所指向的资源时,aa的引用计数+1,都变成2。
(3)当这两个智能指针使用完后,调用析构函数,引用计数都-1,都变成1,由于引用计数不为0,所以aa和bb所指向的对象不会被释放。
(4)当aa所指向的资源释放需要当bb中的a被销毁,就需要bb资源的释放,bb所指向的资源释放就需要当aa中的b被销毁,就需要aa资源的释放。因此aa和bb都有对方的“把柄”,这两个就造成循环引用现象,最终这aa和bb资源就不会进行释放。
因此就需要通过weak_ptr来解决。
(4)weak_ptr
weak_ptr的对象可以指向shared_ptr,并且不会改变shared_ptr的引用计数,一旦最后一个shared_ptr被销毁时,对象就会被释放。
实例:输出结果都是1,
struct A
{
weak_ptr<A> a;
weak_ptr<A> b;
};
int main()
{
shared_ptr<A> aa(new A);
shared_ptr<A> bb(new A);
aa->b = bb;
bb->a = aa;
// use_count(): 返回智能指针对象的引用计数。
cout << "aa的引用计数" << aa.use_count() << endl;
cout << "bb的引用计数" << bb.use_count() << endl;
system("pause");
}
因此在双向链表等有多个指针的时候,结构体内的指针应该定义为weak_ptr,避免出现循环引用。
weak_ptr可以通过expired()函数判断引用计数是否为0,返回true说明对象已经被销毁了。用lock()函数可以将weak_ptr转换为shared_ptr。