关于操作符new()和操作符delete()的问题
Questions about operator new() and operator delete()
考虑以下代码和下面的问题:
/*
* GCC 4.4
*/
#include <iostream>
using namespace std;
class A {
public:
void* operator new(size_t s) {
cout << "A::operator new(size_t) calledn";
}
void operator delete(void* p) {
cout << "A::operator delete(void*) calledn";
}
void* operator new(size_t s, A* p) {
cout << "A::operator new(size_t, A*) calledn";
}
void operator delete(void* p, size_t s) {
cout << "A::operator delete(void*, size_t) calledn";
}
};
void* operator new(size_t s) {
cout << "::operator new(size_t) calledn";
}
void operator delete(void* p) {
cout << "::operator delete(void*) calledn";
}
void* operator new(size_t s, A* p) {
cout << "::operator new(size_t, A*) calledn";
}
void operator delete(void* p, size_t s) {
cout << "::operator delete(void*, size_t) calledn";
}
int main() {
A* p1 = new A(); // See question 1.
delete p1; // See question 2.
A* p2 = new (p1) A(); // See question 3.
delete p2; // See question 4.
}
下面的问题似乎有些多余。然而,我试图区分的是c++标准规则定义的内容和实现定义的内容。
operator new(size_t)
将在任何情况下使用(取自A或取自全局)名称空间,无论是否是默认的)。这没问题。现在试着只删除void* A::operator new(size_t) {}
:为什么编译器给出:错误:没有匹配的函数来调用' A::operator new(unsignedint) '注:候选者有:static void* A::operator new(size_t, A*)
不允许编译器从全局获取
::operator new(size_t)
名称空间?为什么
operator delete(void*)
优先于operator delete(void*, size_t)
(当这两个版本都存在于同一个名称空间,其中operator delete (void*)
是从)?如果我删除
void* A::operator new(size_t, A*)
,为什么代码不能编译?尽管在全局命名空间中定义了相同版本的操作符?错误:没有匹配的函数来调用A::operator new(unsigned int)一个*,)"注:候选者为:static void* A::operator new(size_t)
为什么编译器仍然喜欢
operator delete (void*)
,尽管A::operator new(size_t, A*)
已经被使用为了得到p2?
让我们想象一些场景。首先,下面的总是有效:
A * p1 = ::new A;
::delete p1;
A * p2 = ::new (addr) A; // assume "void * addr" is valid
p2->~A();
全局表达式总是使用全局命名空间中相应的操作符,所以没有问题。注意,没有"place -delete"表达式。每个位置构造对象必须通过调用析构函数显式地销毁。
接下来,假设我们写:
A * p3 = new A;
delete p3;
这一次,分配函数operator new(size_t)
首先在A
的作用域中查找。该名称存在,但是如果您删除了正确的重载,就会出现错误。(这是第1和第3个问题的答案。)operator delete()
也是如此。单参数版本((void *)
)优于双参数版本((void *, size_t)
)并没有特别的原因;你应该选择其中之一。(还要注意,没有全局版本的双参数函数。)
最后,让我们回顾一下位置-new表达式。因为没有位置删除表达式,所以最后一行代码是一个错误(未定义的行为):您不能delete
任何不是通过默认- new
表达式获得的东西。如果定义了放置-新建分配函数,还应该定义匹配的释放函数。这个函数只会在一种特定的情况下被自动调用:如果一个place -new表达式new (a, b, c) Foo;
导致构造函数抛出异常,那么就会调用相应的释放函数。否则,由于所有的位置构造都是手动的,您通常只能手动调用位置释放函数(并且通常根本不会调用,因为它很少做任何实际工作)。
典型的场景可能是这样的:
void * addr = ::operator new(sizeof(Foo)); // do real work
Foo * p = new (addr, true, 'a') Foo; // calls Foo::operator new(void*, bool, char);,
// then calls the constructor Foo::Foo()
// in case of exception, call Foo::operator delete(addr, true, 'a')
p->~Foo();
Foo::operator delete(addr, true, 'a'); // rarely seen in practice, often no purpose
::operator delete(addr); // do real work
回到开头的代码示例,请注意,标准要求全局::operator delete(void *, void *)
不做任何事情。也就是说,global placement-new需要零清理。
关于你的第一个和第三个问题,在我看来是这样的:
- 因为你没有使用::new,编译器试图在a类中找到一个新的操作符。
- 找到一个,但是找不到合适的过载,所以失败。
如果您显式地声明::new
,它应该没有任何问题。只有在类中找不到new操作符的专门化版本时,编译器才会使用全局命名空间。
出自标准:§5.3.4,
9。如果new-expression以一元::操作符开头,则分配函数的名称在全球范围。否则,如果分配的类型是类类型T或其数组,则分配函数为在t的作用域中查找name,如果查找失败,或者分配的类型不是a类类型,分配函数的名称在全局作用域中查找。
对象不记得它是如何创建的;置换delete仅在相应的置换new抛出时使用,否则使用常规的delete操作符。