C++的魅力在于既能通过指针实现较底层地址操作,又能很好体现出面向对象的威力。在享受它的灵活性的同时,你必须具有坚实的C基础,时刻提防指针可能给你带来的灾难。可能很多程序员对此不以为然,对自己的C++水平满怀信心。但是我遇到很多程序员面试的时候就倒在了一个个具体的C++细节上。多年工作的老鸟也不敢轻易说自己的c++水平100分。至于本人自己,因为经常穿梭于C++、C#、managed code混用环境,也有陷入混乱的时候。下面就举一个例子讲讲我最近犯过的一个错误。
问题起始于我发现自己的一段程序经常crash,出现了一个严重的bug。
这是一段什么样的程序呢?我定义的一个类里边要保存一个map集合,map的KeyValuePair中的value部分保存的是一个以数据集和为主的类对象:
{
~StyleInfo(){…};
public:
AcPpStyle *pStyle;
CString strLocStyleName;
CString strDescription;
//color
AcCmColor AcCmColor;
//layer info
CString strLayer;
… …
}
class MyClass
{
… …
private:
std::map<int, StyleInfo> m_StyleMap;
… …
}
pStyle指向堆内存,当我在StyleInfo的析造函数里给指针对指针完成delete操作并赋值为NULL时,我莞尔一笑,庆幸自己时刻提防着内存泄漏。可是程序运行起来之后我就傻了眼。还好本人很快反应过来,以下肯定是问题的所在。
StyleInfo styleLocal = m_StyleMap[index];
局部对象一出作用域,析构函数岂不是就把我辛辛苦苦分配的堆对象析构了?怎么办?哥们灵机一动,反正指针赋指指的是同一地址,我就在在StyleInfo的析构函数里加上逻辑判断,决定什么时候该delete指针,什么时候不该。因为本人进行的是图形开发,new出来的对象有时需要交给图像平台去管理,这样一来程序员就不需要再进行delete操作回收空间,只需把指针赋NULL,否则就必须程序员自己打理。逻辑是复杂的,中午是发困的,10几分钟之后我能想到的漏洞都补齐了。此时我已经把delete相关的代码搬到MyClass的析构函数中了:
StyleInfo style = m_StyleMap[index];
AcPpStyle *pStyle = style.pStyle;
if(Acad::eOk == es && !id.isNull() && id.isValid())
{
if (pStyle->isKindOf(AcPpAnnotationStyle::desc()))
{
AcDbObjectId btrId;
btrId = pStyle->blockTableRecord();
AcPpImpAnnotation::addAnnotationDataToBlockDef(btrId, pStyle->objectId());
}// 以上是对像托付给图形平台的一些逻辑代码
pStyle->close();
pStyle = NULL;
}
MyClass::~ MyClass ()
{
for(int i=0; i<m_StyleMap.size(); i++)
{
AcPpStyle *pStyle = m_StyleMap[i].pStyle;
if(pStyle != NULL)
{
delete pStyle;
pStyle = NULL;
}
}
}
终于可以开心的F5了,怎么回事,又出问题了!哥哥这个时候开始犯糊涂了,难道操作map的时候,StyleInfo类缺省赋值不起作用了?好吧,给StyleInfo重载赋值操作符,明确指针的指向,把指针成员的赋指指向同一个块儿内存。还不行?加一个拷贝构造函数, 还是不行!(我对自己否定自己的C++对象模型知识感到很惋惜!明眼人可以靠到我在犯罪的道路上越走越远。 J)哥们出离愤怒了,这么几行代码就要摧毁我的职业信心?昏昏欲睡的脑袋频率是越来越低。怎么办?喝杯冷水去。
一杯冷水下肚,老袋清醒一圈,原因自明了:
缺省的指针赋指会将其和map中的指针指向同一地址不假。但是当我把local对象中的指针delete或清零时,map中的指针指向的内容确实不存在了,但是指针本身还是指向以前的堆内存地址,此刻如果进行地址访问必将出现问题。因此我必须把map里的指针也同时清零。Oh, my 的 God!
可见C++中的很多东西,你不仅要记规律,更要明白其中的道理,最重要的还是要灵活应用它们,时刻提防着它们。