笔者在学习STL源码的时候发现了一个令我困惑的问题,在STL源码剖析一书中的空间配置器(allocator)章节部分,有这么一段代码:
1 2 3 4 5 6 7 |
... template <class T> inline T* allocate(ptrdiff_t size, T*){ set_new_handle(0); T* tmp = (T*)(::operator new((size_t)(size* sizeof(T)))); ... } |
令我疑惑是第五行代码中operator new ( )怎么是以一个函数的身份在使用?我印象中的new和delete一直是一个运算符(也有翻译成操作符的,即new operator/delete operator)。于是我查阅了cppreference,输入关键字new,得出如下结果:
(图片注:图中的new expression和通常所说的new operator指代都是new操作符(运算符)。更有,operator new 和new expression在MSDN中对应的是operator new Function和new operator,建议使用英文表述)
果真,它们是不同的。那么问题来了,这里的operator new跟我们较常使用的new运算符之间有着哪些千丝万缕的关系呢?这里,先说结论:
使用new运算符时,new运算符会调用特殊函数operator new(operator new Function);同理,使用delete运算符时,会调用特殊函数operator delete。
首先,先从运算符new/delete以及new []/delete []开始说起:
1.new 运算符语法:
[::] new [placement] type-name [initializer]
[::] new [placement] (type-name) [initializer]
其中:
placement:如果重载new,则提供了一种传递附加参数的方式。(实质上,重载是针对new operator运行机制中的第一步分配空间时使用的new operator Function进行重载)
type-name:指定要分配的类型,它可以是内置类型,也可以是用户定义的类型;如果类型规范非常复杂,则可用括号将其括起来以强制实施绑定顺序。
initializer:为初始化对象提供值。不能为数组指定初始值设定项。仅当类具有默认构造函数时,new运算符才会创建对象的数组。
2.delete运算符语法:
[::] delete cast-expression
[::] delete [ ] cast-expression
使用new为C++类对象分配内存时,将在分配内存后调用对象的构造函数;使用delete运算符可解除分配使用new运算符分配的内存。
3.当我们使用new运算符的时候,一定要记得用完之后delete对象。下面我们看一个错误的示例:
1 2 3 4 |
//错误的示例代码 std::string* stringArray = new std::string[100]; ... delete stringArray; |
哪里错了呢,我明明是new和delete搭配使用的啊?这里错误不在new和delete的成对使用上。我们首先要明白,当我们使用new生成一个对象,有两件事情会发生:
(1)内存被分配出来(通过名为operator new的函数)。
(2)针对此内存会有一个(或更多)构造函数被调用。
而当你使用delete运算符时,也会有两件事情发生:
(1)针对此内存会有一个(或多个)析构函数被调用。
(2)然后内存才被释放(通过名为operator delete的函数)。
所以,上面的错误在于delete:被删除的内存之内究竟有多少个对象?这个问题的答案决定了有多少个析构函数必须被调用起来。换言之,当我们delete一个对象时,应该问问自己,我们删除的这个指针是指向单一的一个对象?还是指向一个对象数组?因为单一对象与对象数组的内存布局是不同的——数组所用的内存通常还包括“数组大小”的记录,以便delete知道需要调用多少次析构函数。
所以delete的正确打开方式如下:
1 2 3 4 5 |
std::string* stringPtr1 = new std::string; std::string* stringPtr2 = new std::string[100]; ... delete stringPtr1; //删除一个对象 delete [] stringPtr2; //删除一个由对象组成的数组 |
我们可以得出如下结论:
(1)如果你调用new时使用[ ],那么使用delete时也必须使用[ ]。
(2)如果你调用new时没有使用[ ],那么也不该在delete时使用[ ]。
4.当我们使用typedef时也要尤其小心,考虑下面的示例:
1 2 3 4 5 6 7 |
typedef std::string AddressLines[4]; //每个人的地址有4行,每行是一个string //由于AddressLines是个数组,如果这样使用new: std::string* pa1 = new AddressLines; //注意,new AddressLines返回一个 //string*,就像new string[4]一样 //那就必须匹配数组形式的delete delete pa1; //行为未定义 delete [] pa1; //正确 |
为了避免此类的错误,建议不要对数组形式做typedef动作。
通过上面总结,我们对new/delete运算符有了进一步的了解。同时对operator new和operator delete这两个特殊的函数也有了一个初步的认识。关于这两个特殊的函数,我会在下次总结。
参考:
C++ Primer
cppreference
STL源码剖析
MSDN