1 指针的使用场合 Cases using pointers
在正常情况下,无论从安全性和编程效率、运行效率上比较,指针比普通声明的变量都要低,特别是new和delete的速度相当慢,因此一般不使用指针变量。以下是COODBLib优化前的几个例子:
在Debug版本中选取2E4个CCustomer过程中,需花费:
563.414ms完成300032次CObject::operator new()操作
469.955ms完成140000次delete()操作
而全面优化过后,进行此操作共耗时6~7秒,可见指针空间申请和销毁可能占用大量时间。
但因为对于多态、引用、变量交换等操作指针有其优势,在此类情况下将不得不使用指针;此时要特别注意安全问题和内存回收问题。
2 指针的安全使用 Using pointers safely
以上三条就是指针的使用要点。
- 在指针声明时应习惯性初始化为NULL,如
CRecordset *pRecordset=NULL;- 在类的构造器中应对所有成员指针变量初始化。
检查 (Check state and parameters)
- 检查函数传入参数,如
BOOL SetName(LPCTSTR lpszName)
{
if (lpszName==NULL)
return FALSE;
...
}- 在类成员函数中检查类的指针变量是否可用,即用于外部服务的指针变量是否已经就位。
- 删除指针内存时检查指针是否为空,配合下面的复位指针,可防止多次删除同一个指针内存。
一种例外情况是VC的Collection族类(Array/List/Map)使用时不需要判断,因为其GetAt()函数已经进行了判断。
在删除指针内存后,应复位指针为0,否则在下次检查指针是否为空时可能出错。如:
delete m_pRecordset;
m_pRecordset=NULL;但在脱离作用域前可不复位,如在函数末尾删除临时指针变量及在销毁器删除成员指针变量。
3 指针内存回收 Freeing memory of pointers
函数内指针
如果此指针是临时变量或尽管是类的变量,却只在此函数中才会使用,在函数退出时删除,如果函数退出的情况很复杂,则使用函数销毁器来完成,否则极可能发生漏删现象。参考函数结构规范中的例子。
替别的函数销毁指针是及其危险且难以维护的事情,如:
void main()
{
char *sz=new char[8];
memset(sz, 0, 8);
memcpy(sz, "name", 4);
PrintfName(sz);
//...
}
void PrintName(char *sz)
{
printf("%s", sz);
delete []sz;
}
当上面的两个函数不是同一个程序员维护时,极易发生内存泄漏、重复删除内存等问题。
在函数中回收内存的要点在于:谁申请的内存谁回收。
删除类的内存的要点在于“谁的指针谁回收”,而不是“谁申请的内存谁回收”。因而类只负责回收自己的指针,而不管其父类和支类(CCustomer对于CGroup而言是一个支类)的指针内存是否回收。这使得类的内存回收变得简单,即只需要关心当前类有哪些成员指针变量即可。
类成员指针变量的内存回收一般在销毁器中进行,少量情况下需要在销毁之前删除内存的,可建立FreeMemory()函数,但需要在销毁器中也调用此函数。注意FreeMemoryAll()和销毁器的要求一般略有不同,FreeMemoryAll()是销毁器的一个子集。COODBQuery是个典型的例子。
不过具体到每个成员指针变量比较复杂,有几种情况:
①指针是外部服务的地址,此指针不需要删除,在VC的CRecordset类应用中可看到:
CDatabase Database;
CRecordset *pRecordset=new CRecordset(&Database);
可以看出此类中必有一个指针来存储&Database,但此指针肯定不需要删除内存的操作。
为了能清除的发现此类指针,推荐用m_cp作为其前缀,表明此为Const Pointer。
②指针是内部存储数据,则需要删除。内部存储数据有两种,一种是普通指针,由类的某个函数(如构造器)申请内存;另外一种是PtrArray/List/Map的元素,在类的外部申请,通过Add函数加入,但也在类的销毁器中回收。例如:
template <class Class>
COODBQuery <Class>::~COODBQuery()
{
if (m_pOODBFields)
delete []m_pOODBFields;
m_pOODBFields=NULL;
FreeMemoryAll();
}
template <class Class>
void COODBQuery <Class>::FreeMemoryAll()
{
//cy: Free all memory.
for (int i=m_ClassArray.GetSize()-1;i>=0;i--)
delete m_ClassArray.GetAt(i);
RemoveAll(); //cy: This function will transfer class pointers from ClassArray to ClearedArray.
}
可以看到,遵循“谁的指针谁回收”的原则,尽管m_ClassArray.GetAt(i)(实际上是一个从COODBRecord继承的类的指针,如CCustomer)中可能还有指针变量,但其回收工作已经交由Class销毁器完成。这样封装的好处是无论Class的内部结构如何,是否发生变化,只要在Class销毁器中做好内存回收工作即可。
由于全局指针首先是一个全局变量,所以一般情况下会随着程序结束而自动被释放内存,但为了在debug下不会因此而混淆程序中其他未得到回收的内存,建议进行内存回收,其位置在ExitInstance()函数内进行。
大部分全局指针都是静态指针,所以在命名上没有特殊要求,用g_p作为前缀即可。