类和结构中的指针悬挂
在类和结构中的指针则更容易出危险。我们假设要设计一个字符串类如下:
class CMyString
{
protected:
int m_nSize;
char* m_pData;
public:
CMyString(const char * pStr);
virtual ~CMyString();
};
CMyString::CMyString(const char * pstr)
{
m_nSize = strlen(pstr);
m_pData = new char[m_nSize +1];
strcpy(m_pData,pstr);
}
CMyString::~CMyString()
{
delete[]m_pData
}
这段代码似乎没有问题,实际上隐含了很严重的问题。考虑下面这段代码:
CMyString GetString()
{
CMyString str1 = "haha";
CMyString str2 = "xixi";
CMyString str3 = str1;
str2 = str1;
return str3;
}
上述代码中,1、2行分别初始化了一个CMyString对象,第三行则使用str1来初始化str3,第四行则使用等于号赋值。下面对于
第三行和第四行分别说明问题。
拷贝构造函数问题
对于第三行,系统调用CMyString的拷贝构造函数来初始化str3,其调用格式等价于
CMyString str3(str1);
由于CMyString没有提供拷贝构造函数,编译器会把str1的内容原原本本的复制给str3,这样,str1.m_pData这个指针也
被复制给str3了。在函数退出时,str1首先析构(顺序和编译器有关,这里只是一个假设,其实谁先析构问题都一样),
其析构函数删除了m_pData,此时str3的m_pData就成为一个悬挂指针。当str3析构时,它试图删除m_pData必然造成一个异常。
对于GetString函数本身而言,它返回了str3对象,返回过程会创建一个CMyString临时对象,并用str3作为参数调用拷贝构造
函数,其结果和前面所述一样。
operator=问题
在第四行,程序使用=运算符给str2赋值。由于类没有提供operator =,编译器缺省实现是把str1的内容完全复制给str2,
这样导致的后果和前面是一样的。
避免指针悬挂的要点
不保存指针的拷贝,如果要保存指针指向的内容,则新分配一块大小相等的内存,把其内容完全拷贝过去。如果确实不得不
保存拷贝,必须小心关注每个拷贝的使用情况,一旦一个拷贝进行了删除,则其他拷贝必须立刻放弃使用。
包含指针的结构和类必须实现拷贝构造函数和operator=运算符,如果不愿意实现,对其赋值和构造必须
针对每个成员变量进行特殊处理。
如果结构或者类的成员变量直接或者间接的包含指针,也必须如前一条处理(例如结构中定义了string成员)
指针使用完毕并删除后,应该把指针变量的值设置为0。
改正:
class CMyString
{
public:
int m_nSize;
char* m_pData;
CMyString(const char * pStr);
CMyString(CMyString& pMy);
virtual ~CMyString();
operator=(const CMyString& pMy);
};
CMyString::CMyString(const char * pstr)
{
m_nSize = strlen(pstr);
m_pData = new char[m_nSize +1];
strcpy(m_pData,pstr);
}
CMyString::CMyString(CMyString& pMy)
{
m_nSize = strlen(pMy.m_pData);
m_pData = new char[m_nSize +1];
strcpy(m_pData,pMy.m_pData);
}
CMyString::~CMyString()
{
delete[] m_pData;
}
CMyString::operator=(const CMyString& pMy)
{
m_nSize = strlen(pMy.m_pData);
m_pData = new char[m_nSize +1];
strcpy(m_pData,pMy.m_pData);
}
CMyString GetString()
{
CMyString str1 = "haha";
CMyString str2 = "xixi";
CMyString str3 = str1;
str2 = str1;
return str3;
}