养成良好的C++编程习惯之内存管理的应用详解
投稿:jingxian
开篇导读
虽然本系列文章定位为科普读物,但本座相信它们不但适合新手们学习借鉴,同时也能引发老鸟们的反思与共鸣。欢迎大家提出宝贵的意见和反馈 ^_^
在开篇讲述本章主要内容之前,本座首先用小小篇幅论述一下一种良好的工作习惯 —— 积累、提炼与求精。在工作和学习的过程中,不断把学到的知识通过有效的方式积累起来,形成自己的知识库,随着知识量的扩大,就会得到从量变到质变的提升。另外还要不断地对知识进行提炼,随着自己知识面的扩大以及水平的提升,你肯定会发现原有知识库存在着一些片面、局限、笨拙甚至错误。这时,就需要你有精益求精精的态度和毅力对知识库进行优化整理。
也许以上这些各位都曾想过去实施,也明白其中的道理,但是自己就是给自己各种堂而皇之的借口不花时间去做。这样说吧,技术之路不好走,这个行业有两项基本要求:1、对软件开发工作本身有很大兴趣;2、耐得住寂寞。两者缺一不可,否则还是趁年轻早点转行吧,要不转做软件行业的销售、产品或者管理也行,总之就不要做开发 ^_^
•虚拟内存(Virtual Memory)
•默认堆和私有堆(Process Heap & Private Heap)
•内存映射文件(File Mapping)
•进程堆栈(Heap,其实就是用 malloc() 或 默认的 new 操作符在 Process Heap 里一小块一小块地割肉 ^_^)
•栈(Stack,内存由调用者或被调用者自动管理)
今天我们的主题是封装,至于每种内存模型的概念和 API 的使用方式在这里就不讲了,Google 一下就知道。其实用 C++ 封装上述前 4 种内存访问的原理都差不多,就是在构造函数或其他操作函数中分配内存,然后再在析构函数中确保内存被正确释放。虚拟内存、默认堆和私有堆的操作方式相似,这里就不一一展示了,有兴趣的朋友可以参考本座前几天发表的那篇无人问津的文章:《C++ 封装私有堆(Private Heap)》,哎!下面对内存映射文件的封装也只稍作介绍、我们主要讨论的是使用频率最高的 malloc() 和 new 的封装。
--------------------------------------------------------------------------------
内存映射文件
下面的代码把 File Mapping 句柄以及从 File Mapping 映射的内存分别封装到 CFileMapping 和 CShareMemory 中,可以直接使用 CShareMemory 可以创建一个 File Mapping 以及映射 File Mapping 的内存。
class CFileMapping
{
public:
CFileMapping(
LPCTSTR lpszName,
DWORD dwMaximumSizeLow,
DWORD dwMaximumSizeHigh = 0,
HANDLE hFile = INVALID_HANDLE_VALUE,
DWORD flProtect = PAGE_READWRITE,
LPSECURITY_ATTRIBUTES lpFileMappingAttributes = NULL
)
{
m_hMap = ::CreateFileMapping (
hFile,
lpFileMappingAttributes,
flProtect,
dwMaximumSizeHigh,
dwMaximumSizeLow,
lpszName
);
ASSERT(IsValid());
}
~CFileMapping()
{
if(IsValid())
VERIFY(::CloseHandle(m_hMap));
}
LPVOID ViewMap (
DWORD dwNumberOfBytesToMap,
DWORD dwFileOffsetLow,
DWORD dwFileOffsetHigh = 0,
DWORD dwDesiredAccess = FILE_MAP_ALL_ACCESS
)
{
return ::MapViewOfFile (
m_hMap,
dwDesiredAccess,
dwFileOffsetHigh,
dwFileOffsetLow,
dwNumberOfBytesToMap
);
}
BOOL UnViewMap(LPCVOID lpBaseAddress)
{
return ::UnmapViewOfFile(lpBaseAddress);
}
operator HANDLE () {return m_hMap;}
BOOL IsValid () {return m_hMap != NULL;}
private:
HANDLE m_hMap;
DECLARE_PRIVATE_COPY_CONSTRUCTOR(CFileMapping)
};
class CShareMemory
{
public:
CShareMemory(DWORD dwSize, LPCTSTR lpszName = NULL)
: m_fm(lpszName, dwSize)
{
ASSERT(dwSize > 0);
}
~CShareMemory()
{
for(set<ULONG_PTR>::const_iterator it = m_set.begin(); it != m_set.end(); ++it)
{
LPVOID pV = (LPVOID)*it;
ASSERT(pV);
m_fm.UnViewMap(pV);
}
m_set.clear();
}
LPVOID Alloc(DWORD dwNumberOfBytesToMap, DWORD dwFileOffsetLow)
{
LPVOID pV = m_fm.ViewMap(dwNumberOfBytesToMap, dwFileOffsetLow);
if(pV) m_set.insert((ULONG_PTR)pV);
ASSERT(pV);
return pV;
}
BOOL Free(LPCVOID lpBaseAddress)
{
ASSERT(lpBaseAddress);
set<ULONG_PTR>::iterator it = m_set.find((ULONG_PTR)lpBaseAddress);
if(it != m_set.end())
m_set.erase(it);
return m_fm.UnViewMap(lpBaseAddress);
}
private:
CFileMapping m_fm;
set<ULONG_PTR> m_set;
DECLARE_PRIVATE_COPY_CONSTRUCTOR(CShareMemory)
};
细心的朋友一定会发觉其实这样封装是有缺点的:首先,CShareMemory 只能做内存共享,不能映射到真实文件(hFile 永远为 INVALID_HANDLE_VALUE);第二,可以对 CShareMemory 的 Alloc() 和 Free() 方法进一步封装,利用封装类的析构函数自动调用 Free(),这样就可以完全消除 “set<ULONG_PTR> m_set” 这个属性了;第三,CFileMapping 也可以把文件句柄一起封装进来,这样,从 CreateFile() 到 CreateFileMapping() 都受控了。这个不完美的封装就权当反面教材吧 ^_^
--------------------------------------------------------------------------------
malloc() 系列函数
很多人都建议,在 C++ 中尽量用 new 操作符取代 malloc(),因为 new 类型安全,自动调用构造函数和析构函数等等。关于这点本座略有异议,在某些情形下 malloc() 其实比 new 更好使,效率方面我们可以不计较(几乎所有编译器的 new 操作符都用 malloc() 分配内存),从事过偏底层开发的人都清楚,我们避免不了处理 row data(如:socket 的收发缓冲区等)数据,这类数据是非常适合使用 malloc() 的,用 new 分配的内存还要停顿下来想想到底是用 delete、delete[]、::delete、::delete[] 中的哪个释放,malloc() 分配的内存想都不用想,free() 包打天下,何况人家有 realloc() 可以方便地重新调整内存,你有没有 “renew” 呢?总之一句话,malloc() 的确是有存在的必要,就看接下来我们如何封装它了,请看代码:
// T : 数据类型(内置类型或结构体)
// MAX_CACHE_SIZE : 预申请内存的最大数目,以 sizeof(T) 为单位,如果该值设置合理,对于
// 需要动态递增缓冲区的 buffer 来说能大大提高效率
template<class T, size_t MAX_CACHE_SIZE = 0>
class CBufferPtrT
{
public:
explicit CBufferPtrT(size_t size = 0, bool zero = false) {Reset(); Malloc(size, zero);}
explicit CBufferPtrT(const T* pch, size_t size) {Reset(); Copy(pch, size);}
// 拷贝构造函数要分两种情形
CBufferPtrT(const CBufferPtrT& other) {Reset(); Copy(other);}
template<size_t S> CBufferPtrT(const CBufferPtrT<T, S>& other) {Reset(); Copy(other);}
~CBufferPtrT() {Free();}
T* Malloc(size_t size = 1, bool zero = false)
{
Free();
return Alloc(size, zero, false);
}
T* Realloc(size_t size, bool zero = false)
{
return Alloc(size, zero, true);
}
void Free()
{
if(m_pch)
{
free(m_pch);
Reset();
}
}
template<size_t S> CBufferPtrT& Copy(const CBufferPtrT<T, S>& other)
{
if((void*)&other != (void*)this)
Copy(other.Ptr(), other.Size());
return *this;
}
CBufferPtrT& Copy(const T* pch, size_t size)
{
Malloc(size);
if(m_pch)
memcpy(m_pch, pch, size * sizeof(T));
return *this;
}
// 动态扩大 buffer
template<size_t S> CBufferPtrT& Cat(const CBufferPtrT<T, S>& other)
{
if((void*)&other != (void*)this)
Cat(other.Ptr(), other.Size());
return *this;
}
// 动态扩大 buffer
CBufferPtrT& Cat(const T* pch, size_t size = 1)
{
size_t pre_size = m_size;
Realloc(m_size + size);
if(m_pch)
memcpy(m_pch + pre_size, pch, size * sizeof(T));
return *this;
}
template<size_t S> bool Equal(const CBufferPtrT<T, S>& other) const
{
if((void*)&other == (void*)this)
return true;
else if(m_size != other.Size())
return false;
else if(m_size == 0)
return true;
else
return (memcmp(m_pch, other.Ptr(), m_size * sizeof(T)) == 0);
}
bool Equal(T* pch) const
{
if(m_pch == pch)
return true;
else if(!m_pch || !pch)
return false;
else
return (memcmp(m_pch, pch, m_size * sizeof(T)) == 0);
}
T* Ptr() {return m_pch;}
const T* Ptr() const {return m_pch;}
T& Get(int i) {return *(m_pch + i);}
const T& Get(int i) const {return *(m_pch + i);}
size_t Size() const {return m_size;}
bool IsValid() const {return m_pch != 0;}
// 啊哈,竟然是类型安全的
operator T* () {return Ptr();}
operator const T* () const {return Ptr();}
// 哇塞,竟然还支持索引访问
T& operator [] (int i) {return Get(i);}
const T& operator [] (int i) const {return Get(i);}
bool operator == (T* pv) const {return Equal(pv);}
template<size_t S> bool operator == (const CBufferPtrT<T, S>& other) {return Equal(other);}
// 赋值操作符要分两种情形
CBufferPtrT& operator = (const CBufferPtrT& other) {return Copy(other);}
template<size_t S> CBufferPtrT& operator = (const CBufferPtrT<T, S>& other) {return Copy(other);}
private:
void Reset() {m_pch = 0; m_size = 0; m_capacity = 0;}
size_t GetAllocSize(size_t size) {return max(size, min(size * 2, m_size + MAX_CACHE_SIZE));}
T* Alloc(size_t size, bool zero = false, bool is_realloc = false)
{
if(size >= 0 && size != m_size)
{
size_t rsize = GetAllocSize(size);
if(size > m_capacity || rsize < m_size)
{
m_pch = is_realloc ?
(T*)realloc(m_pch, rsize * sizeof(T)) :
(T*)malloc(rsize * sizeof(T)) ;
if(m_pch || rsize == 0)
{
m_size = size;
m_capacity = rsize;
}
else
Reset();
}
else
m_size = size;
}
if(zero && m_pch)
memset(m_pch, 0, m_size * sizeof(T));
return m_pch;
}
private:
T* m_pch;
size_t m_size;
size_t m_capacity;
};
// 常用 buffer 类型的 typedef
typedef CBufferPtrT<char> CCharBufferPtr;
typedef CBufferPtrT<wchar_t> CWCharBufferPtr;
typedef CBufferPtrT<unsigned char> CByteBufferPtr;
typedef CByteBufferPtr CBufferPtr;
#ifdef _UNICODE
typedef CWCharBufferPtr CTCharBufferPtr;
#else
typedef CCharBufferPtr CTCharBufferPtr;
#endif
嗯。这里要解释一下为何需要两个拷贝构造函数和赋值操作符重载,首先,编译器为不同的模板参数生成不同的类,也就是说:CBufferPtrT<int, 1> 和 CBufferPtrT<int, 2> 被看作是不同的类,另外,C++ 编译器为每个类提供了提供了拷贝构造函数和赋值操作符重载的默认实现(浅拷贝)。因此,上述的第一组拷贝构造函数和赋值操作符重载是改写编译器的默认实现,第二组拷贝构造函数和赋值操作符重载是处理其它类到本类的转换。
测试用例
int _tmain(int argc, _TCHAR* argv[])
{
CBufferPtr buffer;
unsigned char c1 = 'X';
unsigned char pc1[] = "123";
unsigned char pc2[] = "abc";
buffer.Cat(&c1);
buffer.Cat(pc1, 3);
buffer.Cat(pc2, 3);
CBufferPtrT<unsigned char, 10> buffer2 = buffer;
buffer2.Cat(buffer);
buffer2.Realloc(0);
unsigned char* pc = buffer;
const unsigned char& c = buffer[5];
buffer[5] = 'O';
short i1 = 0x7FFF;
short pi0[] = {9,9,9};
short pi1[] = {1,2,3};
short pi2[] = {4,5,6};
short pi3[] = {8,8,8};
CBufferPtrT<short, 10> bufferS(pi0, 3);
bufferS.Cat(&i1);
bufferS.Cat(pi1, 3);
bufferS.Cat(pi2, 3);
bufferS.Cat(pi3, 3);
CBufferPtrT<short, 5> bufferS2;
bufferS2.Malloc(4);
bufferS2 = bufferS;
bufferS2.Realloc(30);
CBufferPtrT<int> bufferI(5, true);
for(size_t i = 0; i < bufferI.Size(); i++)
bufferI[i] = i *10;
bufferI.Malloc();
bufferI[0] = 123;
// 下面这行编译不通过,正好说明这个类是类型安全的
// bufferI = bufferS;
return 0;
}
--------------------------------------------------------------------------------
new & delete
一说到 new 的封装大家立马想到的就是智能指针吧!没错,就是智能指针。但 STL 提供的 auto_ptr 缺陷很多,首先使用起来不方便,竟然连这种写法都不支持:“std::auto_ptr<int> pi = new int;”,天理何在啊!更可恨的是不支持数组指针(需要 delete[]),另外如果某些类重载了 new 操作符的话使用它也有很多问题的,还有其它的很多缺点(我忘记了 ^_^)。不过,C++0x 似乎对智能指针作了重大改进,已经有支持引用计数的智能指针了,但不知是否解决数组指针和区分 delete 与 ::delete 的问题(本座没实测,要是您知道麻烦告诉一声 ^_^)。无论如何,下面代码列出的智能指针支持区分 delete / delete[] / ::delete / ::delete[]。算是 auto_ptr 的改良(也没有使用引用计数),文章篇幅太长了,测试用例就不发了,各位看官自行尝试吧:
/************************************************************************/
/* smart_ptr 单实体或数组智能指针 */
/************************************************************************/
template<class _Ty>
struct simple_deleter
{
static void delete_ptr(_Ty* pv) {delete pv;}
};
template<class _Ty>
struct global_simple_deleter
{
static void delete_ptr(_Ty* pv) {::delete pv;}
};
template<class _Ty>
struct array_deleter
{
static void delete_ptr(_Ty* pv) {delete[] pv;}
};
template<class _Ty>
struct global_array_deleter
{
static void delete_ptr(_Ty* pv) {::delete[] pv;}
};
template<class _Ty, class _Deleter>
class smart_ptr
{
public:
smart_ptr(_Ty* _Ptr = 0) : _Myptr(_Ptr) {}
smart_ptr(smart_ptr<_Ty, _Deleter>& _Right) : _Myptr(_Right.release()) {}
~smart_ptr()
{
reset();
}
smart_ptr<_Ty, _Deleter>& reset(_Ty* _Ptr = 0)
{
if (_Ptr != _Myptr)
{
if(_Myptr)
_Deleter::delete_ptr(_Myptr);
_Myptr = _Ptr;
}
return *this;
}
smart_ptr<_Ty, _Deleter>& reset(smart_ptr<_Ty, _Deleter>& _Right)
{
if (this != &_Right)
reset(_Right.release());
return *this;
}
_Ty* release()
{
_Ty* _Ptr = _Myptr;
_Myptr = 0;
return _Ptr;
}
smart_ptr<_Ty, _Deleter>& operator = (_Ty* _Ptr) {return reset(_Ptr);}
smart_ptr<_Ty, _Deleter>& operator = (smart_ptr<_Ty, _Deleter>& _Right) {return reset(_Right);}
bool is_valid () const {return _Myptr != 0;}
_Ty& operator * () const {return *_Myptr;}
_Ty* get () const {return _Myptr;}
_Ty* operator -> () const {return _Myptr;}
operator _Ty* () const {return _Myptr;}
private:
template<class _Other> smart_ptr<_Ty, _Deleter> (const smart_ptr<_Ty, _Other>&);
template<class _Other> smart_ptr<_Ty, _Deleter>& reset (const smart_ptr<_Ty, _Other>&);
template<class _Other> smart_ptr<_Ty, _Deleter>& operator = (const smart_ptr<_Ty, _Other>&);
template<class _Other> smart_ptr<_Ty, _Deleter> (const smart_ptr<_Other, _Deleter>&);
template<class _Other> smart_ptr<_Ty, _Deleter>& reset (const smart_ptr<_Other, _Deleter>&);
template<class _Other> smart_ptr<_Ty, _Deleter>& operator = (const smart_ptr<_Other, _Deleter>&);
protected:
_Ty* _Myptr;
};
/************************************************************************/
/* smart_simple_ptr 单实体智能指针 */
/************************************************************************/
template<class _Ty>
class smart_simple_ptr : public smart_ptr<_Ty, simple_deleter<_Ty>>
{
public:
smart_simple_ptr(_Ty* _Ptr = 0) : smart_ptr(_Ptr) {}
smart_simple_ptr(smart_simple_ptr<_Ty>& _Right) : smart_ptr(_Right) {}
smart_simple_ptr(smart_ptr<_Ty, simple_deleter<_Ty>>& _Right) : smart_ptr(_Right) {}
smart_simple_ptr<_Ty>& operator = (smart_ptr<_Ty, simple_deleter<_Ty>>& _Right)
{return (smart_simple_ptr<_Ty>&)__super::operator = (_Right);}
smart_simple_ptr<_Ty>& operator = (smart_simple_ptr<_Ty>& _Right)
{return (smart_simple_ptr<_Ty>&)__super::operator = (_Right);}
smart_simple_ptr<_Ty>& operator = (_Ty* _Ptr)
{return (smart_simple_ptr<_Ty>&)__super::operator = (_Ptr);}
private:
template<class _Other> smart_simple_ptr<_Ty> (const smart_ptr<_Ty, _Other>&);
template<class _Other> smart_simple_ptr<_Ty>& operator = (const smart_ptr<_Ty, _Other>&);
template<class _Other> smart_simple_ptr<_Ty> (const smart_simple_ptr<_Other>&);
template<class _Other> smart_simple_ptr<_Ty>& operator = (const smart_simple_ptr<_Other>&);
};
/************************************************************************/
/* smart_gd_simple_ptr 单实体智能指针 (使用全局 delete) */
/************************************************************************/
template<class _Ty>
class smart_gd_simple_ptr : public smart_ptr<_Ty, global_simple_deleter<_Ty>>
{
public:
smart_gd_simple_ptr(_Ty* _Ptr = 0) : smart_ptr(_Ptr) {}
smart_gd_simple_ptr(smart_gd_simple_ptr<_Ty>& _Right) : smart_ptr(_Right) {}
smart_gd_simple_ptr(smart_ptr<_Ty, global_simple_deleter<_Ty>>& _Right) : smart_ptr(_Right) {}
smart_gd_simple_ptr<_Ty>& operator = (smart_ptr<_Ty, global_simple_deleter<_Ty>>& _Right)
{return (smart_gd_simple_ptr<_Ty>&)__super::operator = (_Right);}
smart_gd_simple_ptr<_Ty>& operator = (smart_gd_simple_ptr<_Ty>& _Right)
{return (smart_gd_simple_ptr<_Ty>&)__super::operator = (_Right);}
smart_gd_simple_ptr<_Ty>& operator = (_Ty* _Ptr)
{return (smart_gd_simple_ptr<_Ty>&)__super::operator = (_Ptr);}
private:
template<class _Other> smart_gd_simple_ptr<_Ty> (const smart_ptr<_Ty, _Other>&);
template<class _Other> smart_gd_simple_ptr<_Ty>& operator = (const smart_ptr<_Ty, _Other>&);
template<class _Other> smart_gd_simple_ptr<_Ty> (const smart_gd_simple_ptr<_Other>&);
template<class _Other> smart_gd_simple_ptr<_Ty>& operator = (const smart_gd_simple_ptr<_Other>&);
};
/************************************************************************/
/* smart_array_ptr 数组智能指针 */
/************************************************************************/
template<class _Ty>
class smart_array_ptr : public smart_ptr<_Ty, array_deleter<_Ty>>
{
public:
smart_array_ptr(_Ty* _Ptr = 0) : smart_ptr(_Ptr) {}
smart_array_ptr(smart_simple_ptr<_Ty>& _Right) : smart_ptr(_Right) {}
smart_array_ptr(smart_ptr<_Ty, array_deleter<_Ty>>& _Right) : smart_ptr(_Right) {}
smart_array_ptr<_Ty>& operator = (smart_ptr<_Ty, array_deleter<_Ty>>& _Right)
{return (smart_array_ptr<_Ty>&)__super::operator = (_Right);}
smart_array_ptr<_Ty>& operator = (smart_array_ptr<_Ty>& _Right)
{return (smart_array_ptr<_Ty>&)__super::operator = (_Right);}
smart_array_ptr<_Ty>& operator = (_Ty* _Ptr)
{return (smart_array_ptr<_Ty>&)__super::operator = (_Ptr);}
private:
template<class _Other> smart_array_ptr<_Ty> (const smart_ptr<_Ty, _Other>&);
template<class _Other> smart_array_ptr<_Ty>& operator = (const smart_ptr<_Ty, _Other>&);
template<class _Other> smart_array_ptr<_Ty> (const smart_array_ptr<_Other>&);
template<class _Other> smart_array_ptr<_Ty>& operator = (const smart_array_ptr<_Other>&);
};
/************************************************************************/
/* smart_gd_array_ptr 数组智能指针 (使用全局 delete) */
/************************************************************************/
template<class _Ty>
class smart_gd_array_ptr : public smart_ptr<_Ty, global_array_deleter<_Ty>>
{
public:
smart_gd_array_ptr(_Ty* _Ptr = 0) : smart_ptr(_Ptr) {}
smart_gd_array_ptr(smart_gd_array_ptr<_Ty>& _Right) : smart_ptr(_Right) {}
smart_gd_array_ptr(smart_ptr<_Ty, global_array_deleter<_Ty>>& _Right) : smart_ptr(_Right) {}
smart_gd_array_ptr<_Ty>& operator = (smart_ptr<_Ty, global_array_deleter<_Ty>>& _Right)
{return (smart_gd_array_ptr<_Ty>&)__super::operator = (_Right);}
smart_gd_array_ptr<_Ty>& operator = (smart_gd_array_ptr<_Ty>& _Right)
{return (smart_gd_array_ptr<_Ty>&)__super::operator = (_Right);}
smart_gd_array_ptr<_Ty>& operator = (_Ty* _Ptr)
{return (smart_gd_array_ptr<_Ty>&)__super::operator = (_Ptr);}
private:
template<class _Other> smart_gd_array_ptr<_Ty> (const smart_ptr<_Ty, _Other>&);
template<class _Other> smart_gd_array_ptr<_Ty>& operator = (const smart_ptr<_Ty, _Other>&);
template<class _Other> smart_gd_array_ptr<_Ty> (const smart_gd_array_ptr<_Other>&);
template<class _Other> smart_gd_array_ptr<_Ty>& operator = (const smart_gd_array_ptr<_Other>&);
};
--------------------------------------------------------------------------------
后记
• 对于内存管理,其实还有一种情形还没讲的,就是如何优雅地管理 vetor、list、map 这类容器中的指针,这个话题留到以后讨论 STL 时再详细阐述吧。
•在本座的代码中基本上看不到 free / delere 这类单词(new 则是有的 —— 给智能指针赋值的时候 ^_^),就本座的经验而言,封装如果利用得当确实能减少很多麻烦,使代码更清晰,有条理,降低错误发生几率。
•当然了,封装并不是万能,它不能解决所有问题,关键是靠个人的专注与细心。
•本座码字提出自己的观点,旨在抛砖引玉,激发大家思考如何培养良好的编程习惯,不是权威,更不能尽信。最实在的知识应该来自个人最直接的体验。