关于操作符new()和操作符delete()的问题

Questions about operator new() and operator delete()

本文关键字:操作符 问题 delete new      更新时间:2023-10-16

考虑以下代码和下面的问题:

/*
* 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++标准规则定义的内容和实现定义的内容。

  1. 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)名称空间?

  2. 为什么operator delete(void*)优先于operator delete(void*, size_t)(当这两个版本都存在于同一个名称空间,其中operator delete (void*)是从)?

  3. 如果我删除void* A::operator new(size_t, A*),为什么代码不能编译?尽管在全局命名空间中定义了相同版本的操作符?

    错误:没有匹配的函数来调用A::operator new(unsigned int)一个*,)"注:候选者为:static void* A::operator new(size_t)

  4. 为什么编译器仍然喜欢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需要零清理。

关于你的第一个和第三个问题,在我看来是这样的:

  1. 因为你没有使用::new,编译器试图在a类中找到一个新的操作符。
  2. 找到一个,但是找不到合适的过载,所以失败。

如果您显式地声明::new,它应该没有任何问题。只有在类中找不到new操作符的专门化版本时,编译器才会使用全局命名空间。

出自标准:§5.3.4,

9。如果new-expression以一元::操作符开头,则分配函数的名称在全球范围。否则,如果分配的类型是类类型T或其数组,则分配函数为在t的作用域中查找name,如果查找失败,或者分配的类型不是a类类型,分配函数的名称在全局作用域中查找。

对象不记得它是如何创建的;置换delete仅在相应的置换new抛出时使用,否则使用常规的delete操作符。