C++中动态内存的管理主要是使用new/delete表达式和std::allcator类。为了管理动态内存更加安全,C++11新标准库推出了智能指针。
new/delete
new完成的操作:
(1): 它分配足够存储一个特定类型对象的内存
(2):为它刚才分配的内存中的那个对象设定初始值。(对于内置类型对象,就是默认初始化该它,对应类类型,调用constructor初始化)
delete完成的操作:
(1):销毁给定指针指向的对象
(2):释放该对象的对应内存
常见问题
1.申请的空间没有进行释放,会出现内存泄漏。这种忘记释放的内存如果不退出程序将永远不会归还给系统。解决方法:
- 在离开作用域前释放掉:
int *p=new int(0);
delete p;
p=nullptr;
//释放该指针后指向空,一可以避免再次访问它出现未定义行为,二可以避免再次delete它出现未定义行为。
- 接管申请的空间
int *pa;
{
int *p=new int(0);
pa=p;
}
delete pa;
2.使用已经释放内存的对象解决方法:对已经释放的内存对象赋给一个空指针,在使用前判断是否为空指针。
int *p=new int(0);
delete p;
p=nullptr;//也可=NULL/0
//......
if(p)
{......}
3.同一块内存释放两次,虽然这个问题听起来不太可能发生,但当有两个指针指向同一块内存时,可能就容易发生了。避免方法:delete后直接将内存对象置为空指针。
智能指针的使用及原理
什么是智能指针?
智能指针是一个类,这个类的构造函数中传入一个普通指针,析构函数中释放传入的指针。智能指针的类都是栈上的对象,所以当函数(或程序)结束时会自动被释放,
RALL
RALL是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等)的技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
- 不需要显式地释放资源。
- 采用这种方式,对象所需的资源在其生命期内始终保持有效。
智能指针的原理
- RAII特性
- 重载operator*和opertaor->,从而具有指针一样的行为。
使用RALL思想设计智能指针:
template<class T>
class SmartPtr
{
public:
SmartPtr(T* ptr = nullptr)
: _ptr(ptr)
{}
~SmartPtr()
{
if(_ptr)
delete _ptr;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
struct Date
{
int _year;
int _month;
int _day;
int main()
{
SmartPtr<int> sp1(new int);
*sp1 = 10
cout<<*sp1<<endl;
SmartPtr<int> sparray(new Date);
sparray->_year = 2018;
sparray->_month = 1;
sparray->_day = 1;
}
}
std::auto_ptr
auto_ptr的实现原理:管理权转移/转移资源的思想。在c++98中提供,尽量不要使用。
template<class T>
class AutoPtr
{
public:
AutoPtr(T* ptr = NULL)
: _ptr(ptr)
{}
~AutoPtr()
{
if (_ptr)
delete _ptr;
}
AutoPtr(AutoPtr<T>& ap)
: _ptr(ap._ptr)
{
ap._ptr = NULL;
}
AutoPtr<T>& operator=(AutoPtr<T>& ap)
{
// 检测是否为自己给自己赋值
if (this != &ap)
{
// 释放当前对象中资源
if (_ptr)
delete _ptr;
// 转移ap中资源到当前对象中
_ptr = ap._ptr;
ap._ptr = NULL;
}
return *this;
}
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
private:
T* _ptr;
};
注意:
- 不要使用auto_ptr保存一个非动态开辟空间的指针,因为在作用域结束的时候,会执行智能指针的析构函数,释放这块空间,但非动态的空间又无法释放;
- 不要使用两个auto_ptr指针指向同一个指针,因为会产生额外开销;
- 不要使用auto_ptr指向一个指针数组,因为auto_ptr的析构函数所用的是delete而不是delete[];
- 不要将auto_ptr储存在容器中,因为赋值和拷贝构造后原指针无法使用。
std::unique_ptr
unique_ptr的实现原理:防拷贝,c++11中实现。
template<class T>
class UniquePtr
{
public:
UniquePtr(T * ptr = nullptr)
: _ptr(ptr)
{}
~UniquePtr()
{
if (_ptr)
delete _ptr;
}
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
private:
// C++98防拷贝的方式:只声明不实现+声明成私有
UniquePtr(UniquePtr<T> const &);
UniquePtr & operator=(UniquePtr<T> const &);
// C++11防拷贝的方式:delete
UniquePtr(UniquePtr<T> const &) = delete;
UniquePtr & operator=(UniquePtr<T> const &) = delete;
private:
T * _ptr;
};
std::shared_ptr
shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。
注意
- shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
- 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
- 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
- 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。
#include <thread>
#include <mutex>
template <class T>
class SharedPtr
{
public:
SharedPtr(T* ptr = nullptr)
: _ptr(ptr)
, _pRefCount(new int(1))
, _pMutex(new mutex)
{}
~SharedPtr() { Release(); }
SharedPtr(const SharedPtr<T>& sp)
: _ptr(sp._ptr)
, _pRefCount(sp._pRefCount)
, _pMutex(sp._pMutex)
{
AddRefCount();
}
// sp1 = sp2
SharedPtr<T>& operator=(const SharedPtr<T>& sp)
{
//if (this != &sp)
if (_ptr != sp._ptr)
{
// 释放管理的旧资源
Release();
// 共享管理新对象的资源,并增加引用计数
_ptr = sp._ptr;
_pRefCount = sp._pRefCount;
_pMutex = sp._pMutex;
AddRefCount();
}
return *this;
}
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
int UseCount() { return *_pRefCount; }
T* Get() { return _ptr; }
void AddRefCount()
{
// 加锁或者使用加1的原子操作
_pMutex->lock();
++(*_pRefCount);
_pMutex->unlock();
}
private:
void Release()
{
bool deleteflag = false;
// 引用计数减1,如果减到0,则释放资源
_pMutex.lock();
if (--(*_pRefCount) == 0)
{
delete _ptr;
delete _pRefCount;
deleteflag = true;
}
_pMutex.unlock();
if (deleteflag == true)
delete _pMutex;
}
private:
int* _pRefCount; // 引用计数
T* _ptr; // 指向管理资源的指针
mutex* _pMutex; // 互斥锁
};
关于这个智能指针的一些问题:
循环引用
线程安全
内存泄漏