c++智能指针
C++中有四个智能指针:auto_ptr, shared_ptr, weak_ptr, unique_ptr
,其中后三个是c++11支持,并且第一个已经被c++11弃用。
智能指针从书面意思来说,就是智能。主要是动态内存的使用很容易出问题,要在正确的时间正确释放内存是很困难的。有时我们可能忘了释放内存,会导致内存泄漏;有时我们使用一个已经释放了的内存的指针,会产生引用非法内存的指针。
所以,使用智能指针就可以避免这种情况的方式。
1.auto_ptr类
虽然这个类已经被c++11弃用了,还是简单了解下
#include <iostream>
#include <memory>
using namespace std;
class m_string
{
friend ostream& operator <<(ostream& os,const m_string& _string);
public:
m_string(string s)
{
str = s;
cout<<"creat m_string \n";
}
~m_string()
{
cout<<"delete m_string:"<<str<<endl;
}
string& getStr()
{
return str;
}
void setStr(string s)
{
str = s;
}
void print()
{
cout<<str<<endl;
}
private:
string str;
};
ostream& operator <<(ostream& os,const m_string& _string)
{
os<<_string.str;
return os;
}
int main()
{
auto_ptr<m_string> pString(new m_string("test"));
pString->setStr("hi ");
cout<<*pString<<endl;
pString.get()->print();
pString->getStr() += "xiaoming !";
cout<<*pString<<endl;
pString.reset(new m_string("test"));
cout<<*pString<<endl;
return 0;
}
其中,get()是auto_ptr类的成员函数,返回一个原始指针,成员函数reset()重新绑定指向的对象,会释放原来的对象。
int main()
{
auto_ptr<m_string> p1(new m_string("hi"));
auto_ptr<m_string> p2(new m_string("xiaoming"));
p2 = p1;
cout<<*p2<<endl;
if(p1.get() == NULL)cout<<"p1 = NULL\n";
return 0;
}
可以看出来,使用 = 进行辅助的时候,p2之前的对象会被释放,p2接管p1的内存管理权,p1变成了空指针,而且判断智能指针是否为空,应该使用if(p1.get() == NULL)
。这样一来,感觉auto_ptr有不小的问题。
使用release()释放
int main()
{
auto_ptr<m_string> p1(new m_string("123"));
p1.release();
return 0;
}
release()只是放弃了原来对象的内存管理权,并没有释放对象的内存,要释放对象的内存,应该使用p1.reset();
,是不是感觉很奇怪?难怪c++11要弃用它。
2.share_ptr类
官方文档
share是共享的意思,它使用计数机制来表明有几个指针管理这个资源,使用use_count()成员函数查看资源所有者个数。当指向一个对象的最后一个share_ptr被销毁的时候,share_ptr类会自动销毁此对象。
- 这是share_ptr的一些初始化方法
int main()
{
shared_ptr<m_string> p1(new m_string("p1"));
shared_ptr<m_string> p2 = make_shared<m_string>("p2");
auto p3 = make_shared<m_string>("p3");
auto p4 = p3;
auto p5 = make_shared<pair<m_string,m_string>>( m_string("p5_1"), m_string("p5_2"));
cout<<*p1<<":"<<p1.use_count()<< endl;
cout<<*p2<<":"<<p2.use_count()<< endl;
cout<<*p3<<":"<<p3.use_count()<< endl;
cout<<*p4<<":"<<p4.use_count()<< endl;
cout<<p5->first<<":"<<p5->second<<":"<<p5.use_count()<< endl;
return 0;
}
使用make_shared函数,是最安全的分配和使用动态内存的方法。
- make_shared的赋值
int main()
{
std::shared_ptr<m_string> p1;
std::shared_ptr<m_string> p2 (new m_string("p2"));
// cout<<"*p1: "<<*p1<<endl;
//p1一开始是空的,现在使p1也指向p2的对象,这个时候,因为有两个智能指针指向同一个对象
//使用shared_ptr类的计数器为2.
p1 = p2; // copy
cout<<"*p1: "<<*p1<<" "<<p1.use_count()<<endl;
cout<<"*p2: "<<*p2<<" "<<p2.use_count()<<endl;
cout<<endl;
//cout<<"*p2: "<<*p2<<endl;
//p2指向另外一个对象,这之前对象的计数器减1,即p1的计数器为1
//p2因为指向一个新的对象,所以计数器也是为1
p2 = std::make_shared<m_string> ("p2_1"); // move
cout<<"*p1: "<<*p1<<" "<<p1.use_count()<<endl;
cout<<"*p2: "<<*p2<<" "<<p2.use_count()<<endl;
cout<<endl;
//p1指向另外一个对象,这之前对象的计数器减1,这个时候,之前对象的计数器减少为1,所以调用m_string的解析函数,释放内存
//p1因为指向一个新的对象,所以计数器也是为1
std::unique_ptr<m_string> unique (new m_string("p3_unique"));
p1 = std::move(unique); // move from unique_ptr
cout<<"*p1: "<<*p1<<" "<<p1.use_count()<<endl;
cout<<"*p2: "<<*p2<<" "<<p2.use_count()<<endl;
cout<<endl;
return 0;
}
当p1和p2都没有指向之前的对象的时候,计数器为0,释放对象的内存。
- 成员函数swap()
int main()
{
shared_ptr<m_string> p1 = make_shared<m_string>("p1");
shared_ptr<m_string> p2 = make_shared<m_string>("p2");
cout<<"*p1: "<<*p1<<" "<<p1.use_count()<<endl;
cout<<"*p2: "<<*p2<<" "<<p2.use_count()<<endl;
p1.swap(p2);
cout<<"*p1: "<<*p1<<" "<<p1.use_count()<<endl;
cout<<"*p2: "<<*p2<<" "<<p2.use_count()<<endl;
return 0;
}
3.unique_ptr类
官方文档
要强制销毁unique_ptr类指向的对象,应该使用成员函数reset()或者对其进行赋值操作
int main()
{
unique_ptr<m_string> p1(new m_string("p1"));
//将所有权从p1(p1指向的m_string对象)转移给p2,release将p1制空
unique_ptr<m_string> p2(p1.release());
if(p1 == nullptr)
cout<<"p1 is nullptr"<<endl;
cout<<"*p2:"<<*p2<<endl;
//对p2中的str重新复制成"p2"
p2->setStr("p2");
cout<<"*p2:"<<*p2<<endl;
cout<<"------------\n";
unique_ptr<m_string> p3(new m_string("p3"));
//p2使用成员函数reset()先释放p2原来指向对象的内存
p2.reset(p3.release());
cout<<"*p2:"<<*p2<<endl;
cout<<"------------\n";
//不能直接使用p2.release(),p2不会释放内存,会丢失了指针, 需要一个指针来接管这个内存管理权,
auto p = p2.release();
cout<<"*p:"<<*p<<endl;
delete p;
cout<<"------------\n";
unique_ptr<m_string> p4(new m_string("p4"));
unique_ptr<m_string> p5(new m_string("p5"));
//不能使用p4 = p5;
//不能使用 p5 = p4.release();
p5 = std::move(p4);
if(p4 == nullptr)
cout<<"p4 is nullptr"<<endl;
cout<<"*p5:"<<*p5<<endl;
cout<<"------------\n";
//unique_ptr类的成员函数swap
unique_ptr<m_string> p6(new m_string("p6"));
unique_ptr<m_string> p7(new m_string("p7"));
p6.swap(p7);
cout<<"*p6:"<<*p6<<endl;
cout<<"*p7:"<<*p7<<endl;
cout<<"------------\n";
//判断是否为空
std::unique_ptr<m_string> p8;
std::unique_ptr<m_string> p9 (new m_string("p9"));
if (p8) std::cout << "p8 points to " << *p8 << '\n';
else std::cout << "p8 is empty\n";
if (p9) std::cout << "p9 points to " << *p9 << '\n';
else std::cout << "p9 is empty\n";
cout<<"------------\n";
return 0;
}
4.weak_ptr类
- std::weak_ptr 可以用来避免 std::shared_ptr 的循环引用
- weak_ptr 用来表达临时所有权的概念:当某个对象只有存在时才需要被访问,而且随时可能被他人删除时,可以使用 std::weak_ptr 来跟踪该对象。
- weak_ptr绑定到一个shared_ptr 不会改变shared_ptr 的引用计数。如果最后一个指向对象的shared_ptr 被销毁,那么对象会释放,及时weak_ptr指向该对象,对象也会释放。
class B;
class A
{
public:
shared_ptr<B> _pb;
~A(){cout<<"delete A\n";}
};
class B
{
public:
shared_ptr<A> _pa;
~B(){cout<<"delete B\n";}
};
int test12()
{
shared_ptr<A> pa= make_shared<A>();
shared_ptr<B> pb = make_shared<B>();
pa->_pb = pb;
pb->_pa = pa;
cout<<pa.use_count()<<endl;
cout<<pb.use_count()<<endl;
return 0;
}
class B_2;
class A_2
{
public:
shared_ptr<B_2> _pb;
~A_2(){cout<<"delete A_2\n";}
};
class B_2
{
public:
weak_ptr<A_2> _pa;
~B_2(){cout<<"delete B_2\n";}
};
int test13()
{
shared_ptr<A_2> pa= make_shared<A_2>();
shared_ptr<B_2> pb = make_shared<B_2>();
pa->_pb = pb;
pb->_pa = pa;
cout<<pa.use_count()<<endl;
cout<<pb.use_count()<<endl;
return 0;
}
int main()
{
//m_string03();
test12();
cout<<"------------\n";
test13();
cout<<"------------\n";
return 0;
}
- 可以看出,类A和类B都是相互引用,而且都是使用shared_ptr,导致相互引用,当跳出函数时,pa和pb析构的时候,引用计数都会减1,但是引用计数还是1,导致跳出函数的时候,资源都没有被释放,(A,B析构函数都没有调用)。
- 反之,在类A_2,类B_2中,其中B_2类中是成员_pa是weak_ptr。所以一开始,pa的引用计数是1,pb的引用计数是2。当先析构pb的时候,pb的引用计数减1,变成1,这个时候pb还没有被释放;当轮到析构pa的时候,pa的引用计数减1,变成0,这个时候pa被释放同时也会使pb的引用计数再次减1,这个时候pb的引用计数也变成了0,所以pb也会被释放。