智能指针学习记录
智能指针能够保证我们像使用指针一样使用智能指针但是能够避免内存泄漏,智能指针有四种,我们一 一来看。
auto_ptr
#include<iostream>
#include<memory>
using namespace std;
class Test
{
public:
Test(string s)
{
str = s;
cout<<"Test creat\n";
}
~Test()
{
cout<<"Test delete:"<<str<<endl;
}
string& getStr()
{
return str;
}
void setStr(string s)
{
str = s;
}
void print()
{
cout<<str<<endl;
}
private:
string str;
};
int main()
{
auto_ptr<Test> ptest(new Test("123"));
auto_ptr<Test> ptest2(new Test("456"));
ptest2 = ptest;
ptest2->print();
if(ptest.get() == nullptr)
{
std::cout<<"ptest = nullptr\n";
}
return 0;
}
auto_ptr的特点:
当用auto_ptr ptest给auto_ptr ptest2赋值时即ptest = ptest2,如果ptest原来指向的空间不为nullptr就会释放掉原来的空间,指向ptest2,而ptest2则指向nullptr,所以会看到上面的结果。
int main()
{
auto_ptr<Test> ptest(new Test("123"));
ptest.release();
std::cout << ptest.get() << std::endl;
return 0;
}
这里有一个比较特殊的成员函数release(),ptest调用realse后就指向nullptr了,但是并没有调用析构函数。所以原来的空间并没有释放,这显然不行的,这样会造成内存泄漏,那要怎么做呢?
int main()
{
auto_ptr ptest(new Test(“123”));
ptest.reset();
std::cout << ptest.get() << std::endl;
return 0;
}
事实上ptest就是一个对象,只不过他的初始化需要一个模板参数类型的指针而已,然后在这个模板类里面去重载了*和->操作符。
unique_ptr
unique_ptr实现智能指针的方式是通过里面内含一根真正的指针,但是这个指针只能被一个unique_ptr拥有,在这个unique_ptr离开自己的作用域的时候就在析构函数里面delete掉内存空间从而避免内存泄漏。
#include <iostream>
#include <memory>
struct Task {
int mId;
Task(int id ) :mId(id) {
std::cout<<"Task::Constructor"<<std::endl;
}
~Task() {
std::cout<<"Task::Destructor"<<std::endl;
}
};
int main()
{
// 空对象 unique_ptr
std::unique_ptr<int> ptr1;
// 检查 ptr1 是否为空
if(ptr1 == nullptr)
std::cout<<"ptr1 is empty"<<std::endl;
// 不能通过赋值初始化unique_ptr
// std::unique_ptr<Task> taskPtr2 = new Task(); // Compile Error
// 通过原始指针创建 unique_ptr
std::unique_ptr<Task> taskPtr(new Task(23));
// 检查 taskPtr 是否为空
if(taskPtr != nullptr)
std::cout<<"taskPtr is not empty"<<std::endl;
// 访问 unique_ptr关联指针的成员
std::cout<<taskPtr->mId<<std::endl;
std::cout<<"Reset the taskPtr"<<std::endl;
// 重置 unique_ptr 为空,将删除关联的原始指针
taskPtr.reset();
// 检查是否为空 / 检查有没有关联的原始指针
if(taskPtr == nullptr)
std::cout<<"taskPtr is empty"<<std::endl;
// 通过原始指针创建 unique_ptr
std::unique_ptr<Task> taskPtr2(new Task(55));
if(taskPtr2 != nullptr)
std::cout<<"taskPtr2 is not empty"<<std::endl;
// unique_ptr 对象不能复制
//taskPtr = taskPtr2; //compile error
//std::unique_ptr<Task> taskPtr3 = taskPtr2;
{
// 转移所有权(把unique_ptr中的指针转移到另一个unique_ptr中)
std::unique_ptr<Task> taskPtr4 = std::move(taskPtr2);
// 转移后为空
if(taskPtr2 == nullptr)
std::cout << "taskPtr2 is empty" << std::endl;
// 转进来后非空
if(taskPtr4 != nullptr)
std::cout<<"taskPtr4 is not empty"<<std::endl;
std::cout << taskPtr4->mId << std::endl;
//taskPtr4 超出下面这个括号的作用于将delete其关联的指针
}
std::unique_ptr<Task> taskPtr5(new Task(66));
if(taskPtr5 != nullptr)
std::cout << "taskPtr5 is not empty" << std::endl;
// 释放所有权
Task * ptr = taskPtr5.release();
if(taskPtr5 == nullptr)
std::cout << "taskPtr5 is empty" << std::endl;
std::cout << ptr->mId << std::endl;
delete ptr;
return 0;
}
这个还是比较好理解的,主要注意三个地方,realse不释放释放内存会返回内含的指针,并且释放控制权。reset释放内存,std::move(taskPtr2)实际上是取taskPtr2的值,当做右值引用赋值给taskPtr4 ,我似乎觉得右值引用赋值向值传递。
shared_ptr
从名字share就可以看出了资源可以被多个指针共享,它使用计数机制来表明资源被几个指针共享。可以通过成员函数use_count()来查看资源的所有者个数。出了可以通过new来构造,还可以通过传入auto_ptr, unique_ptr,weak_ptr来构造。当我们调用release()时,当前指针会释放资源所有权,计数减一。当计数等于0时,资源会被释放。
#include<iostream>
#include<memory>
using namespace std;
class Test
{
public:
Test(string s)
{
str = s;
cout<<"Test creat\n";
}
~Test()
{
cout<<"Test delete:"<<str<<endl;
}
string& getStr()
{
return str;
}
void setStr(string s)
{
str = s;
}
void print()
{
cout<<str<<endl;
}
private:
string str;
};
unique_ptr<Test> fun()
{
return unique_ptr<Test>(new Test("789"));
}
int main()
{
shared_ptr<Test> ptest(new Test("123"));//调用构造函数输出Test create
shared_ptr<Test> ptest2(new Test("456"));//调用构造函数输出 Test creat
cout<<ptest2->getStr()<<endl;//输出456
cout<<ptest2.use_count()<<endl;//显示此时资源被几个指针共享,输出1
ptest = ptest2;//"456"引用次数加1,“123”销毁,输出Test delete:123
ptest->print();//输出456
cout<<ptest2.use_count()<<endl;//该指针指向的资源被几个指针共享,输出2
cout<<ptest.use_count()<<endl;//2
ptest.reset();//重新绑定对象,绑定一个空对象,当时此时指针指向的对象还有其他指针能指向就不会释放该对象的内存空间,
ptest2.reset();//此时“456”销毁,此时指针指向的内存空间上的指针为0,就释放了该内存,输出Test delete
cout<<"done !\n";
return 0;
}
这个感觉还是很好理解的,比如ptest = ptest2;这句代码,肯定回去调用shared_ptr这个类里面的=操作符重载,然后肯定会去检查ptest是否指向nullptr,如果不为nullptr就释放内存,重新指向新的地址。
#include<iostream>
#include<memory>
using namespace std;
class B;
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得到释放。也就是waek_ptr就是方便用来转换为shared_ptr的通过lock()函数。
#include<iostream>
#include<memory>
using namespace std;
class B;
class A
{
public:
shared_ptr<A> pa_;
~A()
{
cout<<"A delete\n";
}
};
class B
{
public:
shared_ptr<B> pb_;
~B()
{
cout<<"B delete\n";
}
};
void fun()
{
shared_ptr<B> pb(new B());
shared_ptr<A> pa(new A());
pb->pb_ = pb;
pa->pa_ = pa;
cout<<pb.use_count()<<endl;
cout<<pa.use_count()<<endl;
}
int main()
{
fun();
return 0;
}
当对象里面还有一个shared_ptr指向自己时也是不会释放内存的
参考博客:
https://www.cnblogs.com/wuyepeng/p/9741241.html