我们知道,对于JAVA和PYTHON这种语言,会把所有的东西当做对象处理,并且不用自己管理任何内存,如果需要使用,值需要new即可。这对于普通C/C++程序员来说,简直就是福音。
在C语言中,如果你需要使用堆内存,那么需要如下使用:
void fun()
{
int* p = (int*)malloc(sizeof (int));
if (p == null)
{
//error0
return;
}
if (/*error1*/)
{
free(p);
return;
}
if (/*error2*/)
{
free(p);
return;
}
if (/*error3*/)
{
free(p);
return;
}
free(p);
}
上面这个代码光是看起来就比较蛋疼了;因为C语言需要自己管理内存,如果函数退出,就需要管理自己分配的内存,如果一个函数退出点比较多的话每个地方都需要释放内存。这对于我这种菜鸟级别的程序员来说是很容易就忘记释放而返回的。
为了解决这个问题,C++就提出了智能指针的解决方案。
如果从零出发,我们怎么样来自动管理自己的内存呢?当然一个很直白的想法就是利用类的析构函数自动释放内存。例如:
这个其实就是智能指针的思路,下面来实现一下这个思路。
template<typename T>
class AutoPtr
{
public:
AutoPtr() : ptr(nullptr) {
}
explicit AutoPtr(T* p) : ptr(p) {
}
AutoPtr(const AutoPtr&) = delete;
~AutoPtr()
{
if (ptr != nullptr)
{
delete ptr;
ptr = nullptr;
}
std::cout << "~AutoPtr called" << std::endl;
}
void set(T* p)
{
if (ptr != nullptr)
{
delete ptr;
}
ptr = p;
}
operator bool() {
return ptr != nullptr; }
T* operator->() {
return ptr; }
T& operator*() {
return *ptr; }
private:
T* ptr;
};
void AutoPtrTest()
{
AutoPtr<int> p(new int(100));
std::cout << *p << std::endl;
*p = 200;
std::cout << *p << std::endl;
}
运行结果如下:
100
200
~AutoPtr called
这个就是一个最最简单的利用类的析构函数来管理堆内存的例子,当然这个例子简直简单到不行了,简单到基本不能太大的使用;但是不用急,C++的智能指针已经提供了全部的解决方案了。
shared_ptr
的意思是这个指针是共享的,大家都可以使用;这个里面使用一个引用计数来管理使用者的数目,例如:
shared_ptr
,此时引用计数变为2.shared_ptr
,此时引用计数变为1.shared_ptr
,此时引用计数变为0,当引用计数为0之后,释放内存对象占用的内存。unique_ptr
的意思是这个指针只有我自己能够使用,别人都不能用,所以unique_ptr
不支持拷贝。
shared_ptr
的使用template<class _Ty>
class shared_ptr
: public _Ptr_base<_Ty>
{
}
template<class _Ty>
class _Ptr_base
{
private:
element_type * _Ptr{
nullptr};
_Ref_count_base * _Rep{
nullptr};
}
这里对象和引用计数都是使用指针来操作,这样的话,新建对象的时候就可以简单的值拷贝(拷贝指针)就可以了。
shared_ptr
提供两种方式初始化:
std::make_shared
构造。// make_shared example
#include
#include
int main () {
std::shared_ptr<int> foo = std::make_shared<int> (10);
// same as:
std::shared_ptr<int> foo2 (new int(10));
auto bar = std::make_shared<int> (20);
auto baz = std::make_shared<std::pair<int,int>> (30,40);
std::cout << "*foo: " << *foo << '\n';
std::cout << "*bar: " << *bar << '\n';
std::cout << "*baz: " << baz->first << ' ' << baz->second << '\n';
return 0;
}
这个是指一个指针只能被一个类管理,也就是说,这个类不支持普通的拷贝和赋值(因为底层直接使用浅拷贝,会导致问题)。
template<class _Ty,
class _Dx> // = default_delete<_Ty>
class unique_ptr
: public _Unique_ptr_base<_Ty, _Dx>
{
}
无论是shared_ptr
还是unique_ptr
都是使用delete
来释放内存的,但是如果我们不是使用delete
来释放内存呢?那就需要自己设置释放函数了。
对于释放函数shared_ptr
和unique_ptr
是有点不同的。
这个类的构造函数如下:
//default (1)
constexpr shared_ptr() noexcept;
//from null pointer (2)
constexpr shared_ptr(nullptr_t) : shared_ptr() {
}
//from pointer (3)
template <class U> explicit shared_ptr (U* p);
//with deleter (4)
template <class U, class D> shared_ptr (U* p, D del);
也就是说,我们可以在构造函数中设置释放函数,例如如果我们想用shared_ptr
管理HANDLE
句柄就可以如下使用:
void handle_ptr_delete(PHANDLE pHandle)
{
CloseHandle(*pHandle);
delete pHandle;
}
std::shared_ptr<HANDLE> make_handle_ptr(HANDLE Handle)
{
PHANDLE pHandle = new HANDLE(Handle);
return std::shared_ptr<HANDLE>(pHandle, handle_ptr_delete);
}
int main(int args, char* argv[])
{
HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
std::shared_ptr<HANDLE> Handle = make_handle_ptr(hEvent);
SetEvent(*Handle);
return 0;
}
unique_ptr
的声明如下:
template<class _Ty,
class _Dx> // = default_delete<_Ty>
class unique_ptr
: public _Unique_ptr_base<_Ty, _Dx>
{
}
从声明我们可以知道, unique_ptr
的删除参数是在模板参数中的。
例如使用如下:
void handle_ptr_delete(PHANDLE pHandle)
{
CloseHandle(*pHandle);
delete pHandle;
}
int main(int args, char* argv[])
{
HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
std::unique_ptr<HANDLE, decltype(handle_ptr_delete)*> Handle(new HANDLE(hEvent), handle_ptr_delete);
SetEvent(*Handle);
return 0;
}
shared_ptr
和unique_ptr
的释放函数使用的时候还是有不同的。那么为什么C++为什么会带来这种差异呢?我自己猜测主要是如下原因:
shared_ptr
主要是灵活,shared_ptr
这个指针在使用的时候可以随意改变;那么将删除函数设置为成员,使用上面带来了很大的便利,例如template void reset (U* p, D del);
直接设置相关信息。unique_ptr
这个东西比较固定,无法拷贝和其他操作,主要是用来管理单个内存对象,所有就不用那么灵活的使用了,为了性能上面的考虑unique_ptr
就直接使用了模板参数来设置删除函数(毕竟模板参数在编译期间确定,性能会有所提升)。通过shared_ptr
和unique_ptr
我们可以方便的管理堆内存了;更加重要的一个问题是,不用担心底层抛出异常而跳出内存释放的逻辑(因为析构函数永远都会被调用)。
其实除了shared_ptr
和unique_ptr
之外还有weak_ptr
,weak_ptr
比较特殊,下面专门抽一篇文章来讲解。