上一章说了智能指针的使用。这里还有几个使用上要注意的地方。
首先,必须确保指针指向的是内存池里分配出来的内存块。比如下面这样是不行的。如果这么用的话,指针内容访问的时候,会抛出异常来。因为那个m_nUnmanagedLen
得不到正确值(不是内存池里的内存块,没有合法的前缀),造成m_pData->m_pEndMemPtr
错误。
int A[100];
HEAP_MEM_PTR<int> p = A; // 错误
下面这两个也不行,因为给p1,p2
赋值的时候等号右方没有指向内存块的起始地址,同样得不到正确的内存块的前缀,从而得不到正确的内存可用范围
int* A = NEW int[100];
HEAP_MEM_PTR<int> p1 = &A[16]; // 错误
HEAP_MEM_PTR<int> p2 = A+16; // 错误
再看看下面这个。给p2
赋值的那个之所以是正确的,是因为我们为SmartHeapMemPtr
类重写了operator+函数,里面做了正确的处理。而p1
赋值的那个,仍然是把某个没有指向内存块起始位置的指针赋给了p1
,导致p1
找不到正确的内存块前缀。当然,也许可以重写一下地址符函数来解决这个问题,这个光荣的任务就交给读者自己了。
HEAP_MEM_PTR(int) A = NEW int[100];
HEAP_MEM_PTR<int> p1 = &A[16]; // 错误
HEAP_MEM_PTR<int> p2 = A+16; // 正确
总结一下,要使用HEAP_MEM_PTR
智能指针来检查越界,必须在初始化赋值时要么赋给它另一个HEAP_MEM_PTR
对象,要么赋给它普通的指针,但是这个指针必须是指向有特殊前缀的(即通过内存池分配的)内存块的起始地址。所以,归纳为如下的使用原则:
1. 所有指针定义用HEAP_MEM_PTR
代替,不要出现int* A
这样的语句。
2. 不要用地址符。
3. 以上两条必须贯穿整个程序,包括函数传参和返回
比如下面这个函数
float* Func1(float *p1);
必须改成这样
HEAP_MEM_PTR(float) Func1(HEAP_MEM_PTR(float) p1);
为啥?我如果在其他函数里调用了Func1
,像下面那样
void main()
{
HEAP_MEM_PTR(float) p1;
...
HEAP_MEM_PTR(float) p2 = Func1(p1);
...
}
对于main函数来说,如果Func1
的返回就是个普通指针,怎么保证它指向的是一块有特殊前缀的内存的首地址?无法保证。一旦Func1
做不到这点,以后对p2
的使用就有可能抛出异常。所以还是返回智能指针比较保险。同理,对于Func1
里面的智能指针的使用来说,也必须保证传进来的参数指向有特殊前缀的内存的首地址。
那么如果Func1是第三方库提供的怎么办?你需要知道返回的指针所指的内存长度,并显式的设定给智能指针,如下:
void main()
{
HEAP_MEM_PTR(float) p1;
...
HEAP_MEM_PTR(float) p2 = Func1(p1);
#ifdef USE_SMART_HEAP
p2.SetUnmanagedLength(1024); // 假设我们知道返回的指针指向的是一个1024字节的数组。
#endif
...
}
A同学问了:那如果不知道返回给你的内存的长度怎么办?那还能怎么办,p2
就定义成普通指针吧,就别用智能指针来查越界了。
A同学又问了,上面的程序有个问题啊。我如果要释放p2
所指的内存,DEL_ARRAY
会不会产生问题?当然,因为那内存分配是第三方库管的,不是从我们的内存池分配的。不过释放p2
需要我们来操心么?哪分配哪释放不是应该是C++编程尽可能遵循的原则么?
可是,你也说了那是尽可能。好吧,万一需要我们来释放,就只能把p2
定义成float*
,删除时用delete
了。这么ugly的库,你确定要用吗?