为什么要使用智能指针?
智能指针的作用是管理一个指针,因为存在以下这种情况:申请的空间在函数结束时忘记释放,造成内存泄漏。
使用智能指针可以很大程度上的避免这个问题,因为智能指针就是一个类,当超出了类的作用域是,类会自动调用析构函数,析构函数会自动释放资源。
使用智能指针
class Test {
public:
Test() { cout << "Test的构造函数..." << endl; }
~Test() { cout << "Test的析构函数..." << endl; }
int getDebug() { return this->debug; }
private:
int debug = 20;
};
定义智能指针:
auto_ptr<Test> test(new Test);
智能指针可以像普通指针那样使用:
cout << "test->debug:" << test->getDebug() << endl;
cout << "(*test).debug:" << (*test).getDebug() << endl;
为什么智能指针可以像普通指针那样使用?
因为其里面重载了 * 和 -> 运算符, * 返回普通对象,而 -> 返回指针对象。
智能指针的三个常用函数:
1、get() 获取智能指针托管的指针地址
auto_ptr<Test> test(new Test);
Test *tmp = test.get(); // 获取指针返回
2、release() 取消智能指针对动态内存的托管
auto_ptr<Test> test(new Test);
Test *tmp2 = test.release(); // 取消智能指针对动态内存的托管
delete tmp2; // 之前分配的内存需要自己手动释放
3、reset() 重置智能指针托管的内存地址,如果地址不一致,原来的会被析构掉
auto_ptr<Test> test(new Test);
test.reset(); // 释放掉智能指针托管的指针内存,并将其置NULL
test.reset(new Test()); // 释放掉智能指针托管的指针内存,并将参数指针取代之
reset函数会将参数的指针(不指定则为NULL),与托管的指针比较,如果地址不一致,那么就会析构掉原来托管的指针,然后使用参数的指针替代之。然后智能指针就会托管参数的那个指针了。
auto_ptr(C++98的方案,C++11已经抛弃)
C++11后不建议使用auto_ptr。
auto_ptr<string> p1 (new string ("auto”));
auto_ptr<string> p2;
p2 = p1; //auto_ptr不会报错
此时不会报错,p2剥夺了p1的所有权,但是当程序运行时访问p1指向的内容将会报错。所以auto_ptr的缺点是:存在潜在的内存崩溃问题!
unique_ptr(替换auto_ptr)
unique_ptr实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对象。
它对于避免资源泄露(例如“以new创建对象后因为发生异常而忘记调用delete”)特别有用。
还是上面那个例子:
unique_ptr<string> p3 (new string ("unique"));
unique_ptr<string> p4;
p4 = p3; //此时会报错
编译器认为p4=p3非法,避免了p3不再指向有效数据的问题。因此,unique_ptr比auto_ptr更安全。
万一我就是需要把一个unique_ptr智能指针赋值给另外一个怎么办呢?
可以使用移动函数!如下:
unique_ptr<Obj> ptr1( new Obj() );
unique_ptr<Obj> ptr2( std::move(ptr1) );
这个效果和auto_ptr直接赋值是一样的,就是ptr1不再拥有Obj对象了,所以ptr1不能再用来操作内存中的Obj对象。
unique_ptr和auto_ptr在使用上的区别
1、无法进行左值复制赋值操作,但允许临时右值赋值构造和赋值
unique_ptr<string> p1(new string("I'm Li Ming!"));
unique_ptr<string> p2(new string("I'm age 22."));
cout << "p1:" << p1.get() << endl;
cout << "p2:" << p2.get() << endl;
p1 = p2; // 禁止左值赋值
unique_ptr<string> p3(p2); // 禁止左值赋值构造
unique_ptr<string> p3(std::move(p1));
p1 = std::move(p2); // 使用move把左值转成右值就可以赋值了,效果和auto_ptr赋值一样
2、在 STL 容器中使用unique_ptr,不允许直接赋值
vector<unique_ptr<string>> vec;
unique_ptr<string> p3(new string("I'm P3"));
unique_ptr<string> p4(new string("I'm P4"));
// 必须使用std::move修饰成右值,才可以进行插入容器中
vec.push_back(std::move(p3));
vec.push_back(std::move(p4));
vec[0] = vec[1]; // 不允许直接赋值
vec[0] = std::move(vec[1]); // 需要使用move修饰,使得程序员知道后果
vector<auto_ptr<string>> vec;
auto_ptr<string> p3(new string("I'm P3"));
auto_ptr<string> p4(new string("I'm P4"));
// 必须使用std::move修饰成右值,才可以进行插入容器中
vec.push_back(std::move(p3));
vec.push_back(std::move(p4));
vec[0] = vec[1]; // 风险来了
cout << "vec[1]:" << *vec[1] << endl; // 运行崩溃
3、支持对象数组的内存管理
// 会自动调用delete [] 函数去释放内存
unique_ptr<int[]> array(new int[5]); // 支持这样定义
auto_ptr<int[]> array(new int[5]); // 不能这样定义
shared_ptr
shared_ptr实现共享式拥有概念。多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放。
从名字share就可以看出了资源可以被多个指针共享,它使用计数机制来表明资源被几个指针共享。
除了可以通过new来构造,还可以通过传入auto_ptr、unique_ptr、weak_ptr来构造。当我们调用release()时,当前指针会释放资源所有权,use_count(用指针实现,存在堆上)计数减一。当计数等于0时,资源会被释放。
shared_ptr是为了解决auto_ptr在对象所有权上的局限性(auto_ptr 是独占的),使用引用计数机制提供可以共享所有权的智能指针。
共享指针的使用
初始化
方式一:构造函数
shared_ptr<int> up1(new int(10)); // int(10) 的引用计数为1
shared_ptr<int> up2(up1); // 使用智能指针up1构造up2, 此时int(10) 引用计数为2
方式二:使用make_shared 初始化对象,分配内存效率更高(推荐使用)
make_shared函数的主要功能是在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr。
用法:make_shared<类型>(构造类型对象需要的参数列表);
shared_ptr<int> up3 = make_shared<int>(2); // 多个参数以逗号','隔开,最多接受十个
shared_ptr<string> up4 = make_shared<string>("字符串");
shared_ptr<Person> up5 = make_shared<Person>(9);
引用计数
调用use_count函数可以获得当前托管指针的引用计数。
shared_ptr<Person> sp1;
shared_ptr<Person> sp2(new Person(2));
cout << "sp1 use_count() = " << sp1.use_count() << endl;
cout << "sp2 use_count() = " << sp2.use_count() << endl;
赋值
shared_ptr<int> up1(new int(10)); // int(10) 的引用计数为1
shared_ptr<int> up2(new int(11)); // int(11) 的引用计数为1
up1 = up2; // int(10) 的引用计数减1,计数归零内存释放,up2共享int(11)给up1, int(11)的引用计数为2
主动释放对象
shared_ptr<int> up1(new int(10));
up1 = nullptr; // int(10) 的引用计数减1,计数归零内存释放
// 或
up1 = NULL; // 作用同上
交换
// p1 和 p2 是智能指针
std::swap(p1,p2); // 交换p1和p2管理的对象,原对象的引用计数不变
p1.swap(p2); // 交换p1和p2管理的对象,原对象的引用计数不变
shared_ptr什么时候改变引用计数?
1、构造函数中计数初始化为1;
2、拷贝构造函数中计数值加1;
3、赋值运算符中,左边的对象引用计数减1,右边的对象引用计数加1;
4、析构函数中引用计数减1;
5、在赋值运算符和析构函数中,如果减一后为0,则调用delete释放对象。
weak_ptr
weak_ptr是一种不控制对象生命周期的智能指针,它指向一个shared_ptr管理的对象。进行该对象的内存管理的是那个强引用的shared_ptr。weak_ptr只是提供了对管理对象的一个访问手段。
weak_ptr设计的目的是为配合shared_ptr而引入的一种智能指针来协助shared_ptr工作,它只可以从一个shared_ptr或另一个weak_ptr对象构造,它的构造和析构不会引起引用记数的增加或减少。weak_ptr是用来解决shared_ptr相互引用时的死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。
它是对对象的一种弱引用,不会增加对象的引用计数,和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr。
class A
{
public:
shared_ptr<B> pb_;
~A()
{
cout<<"A delete\n";
}
};
class B
{
public:
shared_ptr<A> pa_;
~B()
{
cout<<"B delete\n";
}
};
void fun()
{
shared_ptr<B> pb(new B());
shared_ptr<A> pa(new A());
pb->pa_ = pa;
pa->pb_ = pb;
cout<<pb.use_count()<<endl;
cout<<pa.use_count()<<endl;
}
int main()
{
fun();
return 0;
}
可以看到fun函数中pa,pb之间互相引用,两个资源的引用计数为2,当要跳出函数时,智能指针pa,pb析构时两个资源引用计数会减一,但是两者引用计数还是为1,导致跳出函数时资源没有被释放(A B的析构函数没有被调用)。
如果把其中一个改为weak_ptr就可以了,我们把类A里面的shared_ptr pb_,改为weak_ptr pb_。这样的话,资源B的引用开始就只有1,当pb析构时,B的计数变为0,B得到释放,B释放的同时也会使A的计数减一,同时pa析构时使A的计数减一,那么A的计数为0,A得到释放。
智能指针有没有内存泄漏的情况?如何解决?
当两个对象相互使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏。
引入weak_ptr弱指针,weak_ptr的构造和析构不会引起引用记数的增加或减少,用来解决shared_ptr相互引用时的死锁问题。
弱指针的使用
weak_ptr wpGirl_1; // 定义空的弱指针
weak_ptr wpGirl_2(spGirl); // 使用共享指针构造
wpGirl_1 = spGirl; // 允许共享指针赋值给弱指针
弱指针也可以获得引用计数:
wpGirl_1.use_count();
弱指针不支持 * 和 -> 对指针的访问:weak_ptr 没有重载 * 和 ->
wpGirl_1->setBoyFriend(); // 编译报错
(*wpGirl_1)->setBoyFriend(); // 编译报错
在必要的使用lock()可以转换成共享指针:
shared_ptr<Girl> sp_girl;
sp_girl = wpGirl_1.lock();
// 使用完之后,再将共享指针置NULL即可
sp_girl = NULL;
智能指针的使用陷阱
不要把一个原生指针给多个智能指针管理
int *x = new int(10);
unique_ptr< int > up1(x);
unique_ptr< int > up2(x);
// 警告! 以上代码使up1、up2指向同一个内存,非常危险
或以下形式:
up1.reset(x);
up2.reset(x);
记得使用release()的返回值
在调用release()时是不会释放是真所指的内存的,这时返回值就是对这块内存的唯一索引,如果没有使用这个返回值释放内存或是保存起来,这块内存就泄漏了。
禁止delete智能指针get函数返回的指针
如果我们主动释放掉get 函数获得的指针,那么智能指针内部的指针就变成野指针了,析构时造成重复释放,带来严重后果!
禁止用任何类型智能指针get函数返回的指针去初始化另外一个智能指针
shared_ptr< int > sp1(new int(10));
// 一个典型的错误用法 shared_ptr< int > sp4(sp1.get());
网友评论