shared_ptr在内部维护一个相当于引用计数的机制,允许多个指针同时指向一个对象。某个指针被销毁之后,引用计数同时较少,当所有指针都被销毁之后,自动释放管理的对象。
本文主要内容仍然参照:参考文献
首先,我们需要知道:智能指针的出现是为了便于程序员对内存进行管理而引入的。为了解决这个问题,C++11标准库引入了三种智能指针,动作类似于常规指针,同时具备在合适的时机自动释放内存的功能(防止内存泄漏)。其次,我们应该理解:智能指针背后的设计思想,其实是将基本类型指针封装为类对象指针(这个类肯定是个模板,以适应不同基本类型的需求),并在析构函数里编写delete语句删除指针指向的内存空间。
本文先主要介绍智能指针之一——shared_ptr,一种采用引用计数机制的智能指针:
1. 预备工作
为了更好地展示智能指针shared_ptr的用法及作用,我们首先构造一个类:
class MyString
{
private:
string text;
public:
MyString(string str) : text(str)
{
cout << "Constructed (" << str << ")!" << endl;
}
~MyString()
{
cout << "Destructed (" << text << ")!" << endl;
}
void Output()
{
cout << text << endl;
}
};
每当这个类的对象被创建和销毁的时候,可以相应的输出Constructed和Destructed字符串,这样我们可以清晰地观察为1变量分配内存和释放内存的时间。
2. shared_ptr的声明、初始化、赋值
首先,我们给出一个简单的实例。由于shared_ptr是一个模版类,在声明时必须声明指向对象的类型:
shared_ptr<MyString> p1; //定义了一个空的shared_ptr,未初始化
shared_ptr<MyString> p2=make_shared<MyString>("Hello ptr ->"); //初始化,最安全的方式是使用make_shared标准库函数
p1 = p2; //赋值
之所以说make_shared这种方式安全,是因为make_shared虽然也生成了MyString对象,并将这个对象直接装配到shared_ptr上。
需要强调的是,shared_prt的本身是一个类,所以它的初始化实际上就是调用shared_ptr类的构造函数。通过分析shared_ptr的源码,我们可以发现以下构造函数:
1)default构造函数:
_LIBCPP_CONSTEXPR shared_ptr() _NOEXCEPT;
//default构造函数:e.g: shared_ptr<int> p; ———— 声明空shared_ptr
2)空指针构造:
_LIBCPP_CONSTEXPR shared_ptr(nullptr_t) _NOEXCEPT;
//由空指针构造:e.g: shared_ptr<int> p(nullptr); ———— 通过空指针构造shared_ptr
3)根据已有指针构造shared_ptr:
explicit shared_ptr (U* p);
//根据已有指针构造shared_ptr:
//shared_ptr<string> p1(new string("First")); ———— 对象创建后直接构造shared_ptr
//string *s=new string("Second");
//shared_ptr<string> p2(s); ———— 对象创建后先临时保存,再构造shared_ptr
根据已有指针构造shared_ptr的两种方式都合法,但是在第二种情况中应该理解,一旦构造了share_ptr,就表明s指向的string对象的内存管理已经交给ps1管理而不应该在通过s访问了(至少是非常小心的访问)。所以,第二种方式应该尽量避免。
4)拷贝构造:
shared_ptr(const shared_ptr& __r) _NOEXCEPT;
//拷贝构造:
//shared_ptr<string> ps1(new strinng("Test")); ———— ps1.use_count()=1;
//shared_ptr<string> ps2(ps1); ———— ps1.use_count() = ps2.use_count()=2; ps1.get()=ps2.get();
拷贝构造函数执行以后,引用计数增加,两个指针指向同一个对象。
5)移动构造:
shared_ptr(shared_ptr&& __r) _NOEXCEPT;
//移动构造:由一个已有的shared_ptr创建新的share_ptr。作为参数的shared_pt同时会释放对内存的管理权利,整个构造过程结束后,引用计数不变。
//shared_ptr<string> ps1(new strinng("Test")); ———— ps1.use_count()=1;
//shared_ptr<string> ps2(move(ps1)); ———— ps1.use_count()=0; ps2.use_count()=1; ps1.get()=0; ps2.get()=****
移动构造中,作为参数的shared_pt同时会释放对内存的管理权利,整个构造过程结束后,引用计数不变。
此外,对于赋值操作,也有以下几种方式:
1)拷贝赋值:从已有指针赋值给shared_ptr,共享数据管理权。(类似于拷贝构造)
shared_ptr& operator= (const shared_ptr& x) noexcept;
2)移动赋值:从已有指针赋值给shared_ptr,伴随数据管理权的转移。移动赋值和拷贝赋值的区别在于参数是否为右值引用。
shared_ptr& operator= (shared_ptr&& x) noexcept;
需要注意的是:make_shared也属于移动赋值。
3. shared_ptr的使用
可以像普通指针一样使用shared_ptr:
p1->Output();
p2->Output();
示例代码:
#include <iostream>
#include <memory>
#include <string>
using namespace std;
class MyString
{
private:
string text;
public:
MyString(string str) : text(str)
{
cout << "Constructed (" << str << ")!" << endl;
}
~MyString()
{
cout << "Destructed (" << text << ")!" << endl;
}
void Output()
{
cout << text << endl;
}
};
int main()
{
MyString mystring("Hello world :-)");
mystring.Output();
shared_ptr<MyString> p1;
//p1->Output();
shared_ptr<MyString> p2=make_shared<MyString>("Hello ptr ->");
//p2->Output();
p1=p2;
p1->Output();
p2->Output();
return 0;
}
/*输出
Constructed (Hello world :-))!
Hello world :-)
Constructed (Hello ptr ->)!
Hello ptr ->
Hello ptr ->
Destructed (Hello ptr ->)!
Destructed (Hello world :-))!
*/
不难发现,代码中没有使用new和delete,但是仍然可以像指针一样使用shared_ptr,而且不需要担心内存泄漏。
4. 小结
shared_ptr的核心思想正如其名称一样——共享,即采用计数机制。为了更好的演示其计数机制,引用知乎上的一个例子:
int main(){
//局部作用域
{
shared_ptr<string> p1(new string("hello world"));
//引用计数=1,即只有一个指针p1引用这块string内存
{
shared_ptr<string> p2=p1;
//引用计数=2,此时有p1、p2两个指针1可以访问这块内存
}
//引用计数=1,超出p2指针作用域,仅p1可访问
}
//引用计数=0,超出p1作用域,没有指针可以访问,资源被释放。
return 0;
}
具体可参考以下代码:
#include <iostream>
#include <memory>
#include <string>
using namespace std;
class MyString
{
private:
string text;
public:
MyString(string str) : text(str)
{
cout << "Constructed (" << str << ")!" << endl;
}
~MyString()
{
cout << "Destructed (" << text << ")!" << endl;
}
void Output()
{
cout << text << endl;
}
};
int main()
{
int flag1=6,flag2=15;
if (flag1>5)
{
shared_ptr<MyString> p1=make_shared<MyString>("Hello");
cout<<"p1.use_count = "<<p1.use_count()<<endl;
cout<<"p1.get = "<<p1.get()<<endl;
cout<<"=========="<<endl;
if (flag2>10)
{
shared_ptr<MyString> p2=p1;
cout<<"p1.use_count = "<<p1.use_count()<<endl;
cout<<"p2.use_count = "<<p2.use_count()<<endl;
cout<<"p1.get = "<<p1.get()<<endl;
cout<<"p2.get = "<<p2.get()<<endl;
}
cout<<"=========="<<endl;
cout<<"p1.use_count = "<<p1.use_count()<<endl;
cout<<"p1.get = "<<p1.get()<<endl;
}
return 0;
}
/*
输出:
Constructed (Hello)!
p1.use_count = 1
p1.get = 0x7f948cc05a18
==========
p1.use_count = 2
p2.use_count = 2
p1.get = 0x7f948cc05a18
p2.get = 0x7f948cc05a18
==========
p1.use_count = 1
p1.get = 0x7f948cc05a18
Destructed (Hello)!
*/