我曾经碰到下面一段代码:
int *p = new (sizeof(int));
编译通不过,提示说是new运算符语法错误。但若换成如下:
int *p = operator new (sizeof(int));
编译顺利通过。一时不得其解。前一句代码显然是调用new运算符;后一句看起来也像是调用new运算符,毕竟其它运算符可以这样调用。
在仔细查看了C++标准后,我才发现这两句代码实际上是不一样的。前一句是调用new运算符,但后一句是直接调用内存分配函数。本文就是要澄清这两者之间的区别。
new运算符有两步操作:
- 调用适当的内存分配函数分配内存
opeator new
或operator new[]
; - 调用适当的构造函数。
同样地,delete
运算符也有两步操作:
- 调用适当的析构函数;
- 调用适当的内存释放函数
operator delete
或operator delete[]
.
C++标准库在头文件<new>
中提供了一些默认的内存分配函数和释放函数,包括会抛出异常的和不抛出异常的版本(见[ISO C++ 1998]的18.4)。严格地来讲,new
和delete
运算符是不能被重载的,重载的只是内存分配函数和释放函数。因此,用户可以自定义内存分配函数和释放函数。注意,内存分配函数和释放函数必须是全局函数(静态或非静态的)或类的成员函数(3.7.3.1, [ISO C++ 1998])。但是,用户不能自定义全局的placement forms的内存分配和释放函数(18.4.1.3 [ISO C++ 1998])。
new和delete运算符及对应的内存分配和释放函数
对于每一个new
及delete
运算符,编译器都将调用相应的内存管理函数。对于用户自定义类型,若用户以类成员函数的形式提供了内存管理函数,则编译器就调用类成员函数,否则编译器调用全局函数。对于定义在类里的成员内存管理函数,不管是否有static关键字声明都是静态成员函数(12.5 [ISO C++ 1998])。
对于new
运算符,调用的内存分配函数的第一个参数是要分配内存的大小。若是放置形式(placement forms)的
new
运算符,new
运算符的放置部分将作为内存分配函数的第二个及后续参数。
T *p = new T; // call operator new(sizeof(T)) T *p = new T[5]; // call operator new[](sizeof(T)+delta) T *p = new (2, f) T; // call operator new(sizeof(T), 2, f
delete
运算符传给内存释放函数的第一个参数是将要释放的内存指针。若调用的内存释放函数是类的成员函数且是两参数风格(有两个参数,第一个参数是指针,第二个参数是内存大小),则内存的大小作为第二个参数(clause 5 of 12.5, [ISO C++ 1998])。delete运算符没有放置形式。
delete p; // call operator delete(p) or operator delete(p, sizeof(*p)) delete[] p; // call operator delete[](p) or operator delete[](p, sizeof(*p)*s+delta)
若释放的对象不是数组,则delete
的操作数的动态类型与静态类型要么一致,要么其析构函数是虚拟的;若释放的对象是数组,则delete[]操作数的动态类型有静态类型必须一致。(5.3.5, [ISO C++ 1998])
内存释放函数虽然是静态的,不能动态绑定,但是若类T的析构函数是虚拟的则编译器将根据指针T *p的动态类型来决定调用合适的内存释放函数(clause 7 of 12.5, [ISO C++ 1998])。例如:
struct B { virtual ~B(); void operator delete(void *); }; struct D : B { void operator delete(void *); }; void f() { B *bp = new D; delete bp; // use D::operator delete(void *), not B::operator delete(void *) }
若new
运算符在初始化对象时抛出异常并且存在与operator new
匹配的内存释放函数,那么这个内存释放函数将被调用以释放内存,异常在new
表达式的环境下继续传播。delete
运算符没有放置形式,但是存在放置形式的内存释放函数。非放置形式的内存释放函数与非放置形式的内存分配函数是匹配的;放置形式的内存释放函数与放置形式的内存分配函数,若它们的第二个及后续参数一致,则是匹配的。
class A { public: A() { thrown exception(""); } void operator delete(void *p, size_t s) { cout << "A::operator delete/n"; } }; int main() { try { A *p = new A; // 抛出异常!调用A::operator delete() } catch (exception) { } return 0; }
由于A的构造函数会抛出异常,而且A定义了一个与operator new(size_t)
匹配的内存释放函数,因此编译器将调用那个内存释放函数然后抛出异常。
References
- [ISO C++ 1998] ISO C++ Standard, 1998.
Updated: 2011-04-03 16:38