1. 概念
C++堆内存对象在new之后使用,如果忘记delete则会产生内存泄漏的问题。
诸如Java、C#等语言直接提供垃圾回收机制来处理不使用的对象,因此在C++98中引入了智能指针的概念,并在C++11中趋于完善。
使用智能指针可以让堆内存对象不调用delete就能被销毁。
智能指针的原理是通过一个栈内存的智能指针对象,控制被管理的堆内存对象生命周期,当栈内存的智能指针对象销毁时,在析构函数中释放被管理管理的堆内存对象。这样程序员就不需要手动调用delete释放堆内存对象了。
C++中有四种只能指针:
- auto_ptr 自动指针(C++98),已废弃。
- unique_ptr 唯一指针(C++11)
- shared_ptr 共享指针(C++11)
- weak_ptr 虚指针(C++11)
智能指针的使用需要引入头文件 #include <memory>
2. auto_ptr
C++98的智能指针,目前已经不推荐使用。
#include <iostream>
#include <memory> // 头文件
using namespace std;
/**
* @brief The Source class
* 被智能指针管理的堆内存资源对象类
*/
class Source
{
private:
string name;
public:
Source(string name):name(name)
{
cout << name << "构造函数" << endl;
}
~Source()
{
cout << name << "析构函数" << endl;
}
void show()
{
cout << name << "成员函数" << endl;
}
};
int main()
{
Source* src = new Source("A");
// 创建一个智能指针对象ap1,管理对象A
auto_ptr<Source> ap1(src);
// 上面的操作可以一步完成
auto_ptr<Source> ap2(new Source("B"));
// 被管理的对象可以通过getter拿出来
ap2.get()->show(); // B
src = ap2.get();
src->show(); // B
// delete src; 乱码:对象内存不能释放两次
// 解除ap1对A的管理,发返回值为对象A
src = ap1.release();
src->show(); // A
delete src; // 因为A回到手动模式,所以可以delete销毁
// 解除ap2对B的管理,同时销毁B
ap2.reset();
auto_ptr<Source> ap3(new Source("C"));
// 使用新的资源对象D替代C,C被销毁
ap3.reset(new Source("D"));
cout << "主函数结束" << endl;
return 0;
}
auto_ptr真正容易误用的地方是其不常用的复制语义(拷贝构造函数和赋值运算符),与浅拷贝的区别是,auto_ptr的复制语义不会让多个auto_ptr共享一个资源,而是会出现控制权的转移。
#include <iostream>
#include <memory> // 头文件
using namespace std;
/**
* @brief The Source class
* 被智能指针管理的堆内存资源对象类
*/
class Source
{
private:
string name;
public:
Source(string name):name(name)
{
cout << name << "构造函数" << endl;
}
~Source()
{
cout << name << "析构函数" << endl;
}
void show()
{
cout << name << "成员函数" << endl;
}
};
int main()
{
auto_ptr<Source> ap1(new Source("A"));
cout << ap1.get() << endl; // 0xfa1060
auto_ptr<Source> ap2(ap1); // 拷贝构造函数
cout << ap1.get() << " " << ap2.get() << endl; // 0 0xfa1060
auto_ptr<Source> ap3;
ap3 = ap2; // 赋值运算符
cout << ap1.get() << " " << ap2.get()
<< " " << ap3.get() << endl; // 0 0 0xfa1060
cout << "主函数结束" << endl;
return 0;
}
3. unique_ptr
作为对auto_ptr的改进,unique_ptr对其持有的堆内存对象具有唯一控制权,即不可以转移到其他智能指针管理。
上面的代码中可以看到,所有复制语义都被屏蔽了。
unique_ptr可以增加move函数达到之前控制权转移的效果。
#include <iostream>
#include <memory> // 头文件
using namespace std;
/**
* @brief The Source class
* 被智能指针管理的堆内存资源对象类
*/
class Source
{
private:
string name;
public:
Source(string name):name(name)
{
cout << name << "构造函数" << endl;
}
~Source()
{
cout << name << "析构函数" << endl;
}
void show()
{
cout << name << "成员函数" << endl;
}
};
int main()
{
unique_ptr<Source> up1(new Source("A"));
cout << up1.get() << endl; // 0xfa1060
unique_ptr<Source> up2(move(up1)); // 拷贝构造函数
cout << up1.get() << " " << up2.get() << endl; // 0 0xfa1060
unique_ptr<Source> up3;
up3 = move(up2); // 赋值运算符
cout << up1.get() << " " << up2.get()
<< " " << up3.get() << endl; // 0 0 0xfa1060
// 如果两个unique_ptr对象交换资源,可以使用swap函数
unique_ptr<Source> up4(new Source("B"));
unique_ptr<Source> up5(new Source("C"));
up4.swap(up5);
up4.get()->show();
up5.get()->show();
cout << "主函数结束" << endl;
return 0;
}
4. shared_ptr
shared_ptr可以把持有的资源在多个智能指针之间共享。
shared_ptr除了可以使用构造函数创建外,还可以使用make_shared函数创建。
#include <iostream>
#include <memory> // 头文件
using namespace std;
/**
* @brief The Source class
* 被智能指针管理的堆内存资源对象类
*/
class Source
{
private:
string name;
public:
Source(string name):name(name)
{
cout << name << "构造函数" << endl;
}
~Source()
{
cout << name << "析构函数" << endl;
}
void show()
{
cout << name << "成员函数" << endl;
}
};
int main()
{
shared_ptr<Source> sp1(new Source("A"));
shared_ptr<Source> sp2 = make_shared<Source>("B");
cout << "主函数结束" << endl;
return 0;
}
make_shared函数创建相比于普通的构造函数:
- 性能更好
- 更加安全
- 可能内存延迟释放
更推荐使用make_shared函数创建shared_ptr对象。
每个shared_ptr内部都多一个隐藏的成员变量,这个变量通常被称为“引用计数”,每多一个shared_ptr持有资源就计数+1,每少一个shared_ptr持有资源就计数-1,当计数从1变为0时,释放资源。
持有相同资源的shared_ptr对象,引用计数一定相同。
#include <iostream>
#include <memory> // 头文件
using namespace std;
/**
* @brief The Source class
* 被智能指针管理的堆内存资源对象类
*/
class Source
{
private:
string name;
public:
Source(string name):name(name)
{
cout << name << "构造函数" << endl;
}
~Source()
{
cout << name << "析构函数" << endl;
}
void show()
{
cout << name << "成员函数" << endl;
}
};
int main()
{
shared_ptr<Source> sp1 = make_shared<Source>("A");
cout << sp1.use_count() << endl; // 1
shared_ptr<Source> sp2(sp1); // 拷贝构造函数
cout << sp1.use_count() << " " << sp2.use_count() << endl; // 2 2
sp2.reset(); // 解除sp2对A的管理
cout << sp1.use_count() << " " << sp2.use_count() << endl; // 1 0
shared_ptr<Source> sp3;
sp3 = sp1; // 赋值运算符
cout << sp1.use_count() << " " << sp3.use_count() << endl; // 2 2
shared_ptr<Source> sp4 = sp3; // 拷贝构造函数
cout << sp1.use_count() << endl; // 3
// shared_ptr<Source> sp5(sp4.get()); 错误
sp4.get()->show();
{ // 局部代码块
shared_ptr<Source> sp6;
sp6 = sp1;
cout << sp1.use_count() << endl; // 4
}
cout << sp1.use_count() << endl; // 3
cout << "主函数结束" << endl;
return 0;
}
5. weak_ptr
weak_ptr本身无法独立工作,但是可以配合shared_ptr进行工作,weak_ptr对资源对象的管理是一种“弱引用”,因为不会影响引用计数。
#include <iostream>
#include <memory> // 头文件
using namespace std;
/**
* @brief The Source class
* 被智能指针管理的堆内存资源对象类
*/
class Source
{
private:
string name;
public:
Source(string name):name(name)
{
cout << name << "构造函数" << endl;
}
~Source()
{
cout << name << "析构函数" << endl;
}
void show()
{
cout << name << "成员函数" << endl;
}
};
int main()
{
shared_ptr<Source> sp1 = make_shared<Source>("A");
weak_ptr<Source> wp1 = sp1;
cout << sp1.use_count() << endl; // 1
cout << wp1.use_count() << endl; // 1
// wp1.get(); 无法获取资源
sp1.reset(); // 计数1→0,对象A销毁
cout << wp1.use_count() << endl; // 0
cout << "主函数结束" << endl;
return 0;
}
可以使用expired函数检测虚指针对资源持有的有效性,即资源对象是否已销毁。如果资源对象没有被销毁,可以使用lock函数生成一个shared_ptr对象共享资源。
#include <iostream>
#include <memory> // 头文件
using namespace std;
/**
* @brief The Source class
* 被智能指针管理的堆内存资源对象类
*/
class Source
{
private:
string name;
public:
Source(string name):name(name)
{
cout << name << "构造函数" << endl;
}
~Source()
{
cout << name << "析构函数" << endl;
}
void show()
{
cout << name << "成员函数" << endl;
}
};
int main()
{
shared_ptr<Source> sp1 = make_shared<Source>("A");
weak_ptr<Source> wp1 = sp1;
// sp1.reset();
shared_ptr<Source> sp2;
// lock能用的前提是资源对象还在
if(wp1.expired())
{
cout << "资源已失效" << endl;
}else
{
sp2 = wp1.lock(); // 返回一个shared_ptr<Source>,但是是临时的,需要保存下来
cout << sp1.use_count() << " " <<
sp2.use_count() <<" " << wp1.use_count() << endl; // 2 2 2
}
cout << "主函数结束" << endl;
return 0;
}
6. 窥探源码(了解)
经过上述学习可以得到结论:智能指针使用模板实现,堆内存对象和引用计数分别是智能指针对象的成员变量,可以通过sizeof观察到。
#include <iostream>
#include <memory> // 头文件
using namespace std;
/**
* @brief The Source class
* 被智能指针管理的堆内存资源对象类
*/
class Source
{
private:
string name;
public:
Source(string name):name(name)
{
cout << name << "构造函数" << endl;
}
~Source()
{
cout << name << "析构函数" << endl;
}
void show()
{
cout << name << "成员函数" << endl;
}
};
int main()
{
auto_ptr<Source> ap1(new Source("A"));
cout << sizeof(ap1) << endl; // 4
unique_ptr<Source> up1(new Source("N"));
cout << sizeof(up1) << endl; // 4
shared_ptr<Source> sp1 = make_shared<Source>("S");
cout << sizeof(sp1) << endl; // 8
weak_ptr<Source> wp1 = sp1;
cout << sizeof(wp1) << endl; // 8
return 0;
}
下面手搓一个简化版的SharedPtr类,简单了解一下智能指针的内部构造,包含以下功能:
- 构造函数
- 拷贝构造函数
- 赋值运算符
- get函数
- use_count函数
- reset函数
- 析构函数
#include <iostream>
#include <memory> // 头文件
using namespace std;
/**
* @brief The Source class
* 被智能指针管理的堆内存资源对象类
*/
class Source
{
private:
string name;
public:
Source(string name):name(name)
{
cout << name << "构造函数" << endl;
}
~Source()
{
cout << name << "析构函数" << endl;
}
void show()
{
cout << name << "成员函数" << endl;
}
};
template <class T>
class SharedPtr
{
private:
T* res = NULL;
int* count = NULL;
public:
// 构造函数
SharedPtr(T* t):res(t),count(new int(1)){}
SharedPtr(const SharedPtr& sp):res(sp.res),count(sp.count)
{
(*count)++; // 因为使用的是拷贝构造函数,因此计数+1
}
SharedPtr& operator =(const SharedPtr& sp)
{
if(this != &sp) // 避免自赋值
{
// 如果之前有资源
// 如果计数>1,计数-1
// 如果计数=1,计数-1,变0,销毁
reset();
res = sp.res;
count = sp.count;
(*count)++;
}
return *this;
}
void reset()
{
// 如果之前有资源
if(res != NULL)
{
// 计数-1
(*count)--;
if(*count == 0) // 减完之后如果计数为0
{
delete res;
delete count;
}
res = NULL;
count = NULL;
}
}
T* get() const
{
return res;
}
int use_count() const
{
return *count;
}
~SharedPtr()
{
reset();
}
};
int main()
{
SharedPtr<Source> sp1(new Source("A"));
SharedPtr<Source> sp2(new Source("B"));
sp2 = sp1; // A销毁,sp2持有B
return 0;
}