问题聚焦:
我们都知道,new和delete要成对使用,但是有时候,事情往往不是按我们预期的那样发展。
对于单一对象和对象数组,我们要分开考虑
遇到typedef时,也需要搞清楚,是单一对象类型还是对象数组类型
来看一个例子:
std::string* stringArray = new std::string[100]; ... delete stringArray;
问题:stringArray所含的100个string对象中的99个可能并没有被适当地删除,因为它们的析构函数很可能没有被调用。
我们来了解一下,使用new时发生了什么,一共有两个动作:
- 内存被分配出来
- 针对此内存会有一个或更多个构造函数被调用
使用delete,也有两个动作:
- 针对此内存会有一个或更多个析构函数被调用
- 内存被释放
简单地说就是:
即将被删除的那个指针,所指的是单一对象,还是对象数组。
内存分布:单一对象和对象数组的内存分布
正如上图所示,数组所用的内存往往还包括“数组大小”的记录,以便delete知道需要调用多少次析构函数。
当对着一个指针使用delete,唯一能够让它知道当前是删除单一对象还是对象数组的方法就是,由你来告诉它。
如下面这样:
std::string* stringPtr1 = new std::string; std::string* stringPtr2 = new std::string[100]; ... delete stringPtr1; delete[] stringPtr2;
如果对stringP1使用delete[] 这种形式,结果也不一定让人愉快,因为它读取的数组大小显然是错误的。
那我们应该怎么办呢?解决方案很简单:
如果你调用new时,使用new[] 的形式,那么对应调用delete时也使用[],
如果你调用delete时,没有使用new[] 的形式,那么对应调用delete时也不应该使用[] 形式。
当你设计的class含有一个指针指向动态分配内存,并提供多个构造函数时,上述的规则尤为重要。
这时,你需要小心地在所有构造函数中使用相同形式的new将指针成员初始化,同时,相应的析构函数也要使用相同的形式。
当使用typedef时,要变得尤为敏感,因为你必须要明确地知道,这时的new是一个什么样的形式:
typedef std::string AddressLines[4]; // 每个人的地址有4行,每行是一个string // 这时候AddresLines是一个数组,所以new时,应该是[]形式 std:string* pal = new AddresLines; // 相当于:new string[4]; //那么,必须匹配数组形式的delete delete pal; // error,行为未定义 delete[] pal; // pass
所以,最好尽量不要对数组形式使用typedef动作。
小结:
- 如果在new表达式中使用[],必须在相应的delete表达式中也使用[]
- 如果在new表达式中不使用[],一定不要再相应的delete表达式中使用[]
- 尽量不要对对象数组使用typedef动作
参考资料:
《Effective C++ 3rd》