关于operator new()和operator delete()的问题

时间:2011-11-21 23:38:30

标签: c++

请考虑以下代码和以下问题:

/*
* GCC 4.4
*/
#include <iostream>

using namespace std;

class A {
public:

    void* operator new(size_t s) {
        cout << "A::operator new(size_t) called\n";
    }

    void operator delete(void* p) {
        cout << "A::operator delete(void*) called\n";
    }

    void* operator new(size_t s, A* p) {
        cout << "A::operator new(size_t, A*) called\n";
    }

    void operator delete(void* p, size_t s) {
        cout << "A::operator delete(void*, size_t) called\n";
    }

};

void* operator new(size_t s) {
    cout << "::operator new(size_t) called\n";
}

void operator delete(void* p) {
    cout << "::operator delete(void*) called\n";
}

void* operator new(size_t s, A* p) {
    cout << "::operator new(size_t, A*) called\n";
}

void operator delete(void* p, size_t s) {
    cout << "::operator delete(void*, size_t) called\n";
}

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(unsigned   INT)”   注意:候选者是:static void * A :: operator new(size_t,A *)

    Connot编译器从全局中获取::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,   A *&安培)”        注意:候选者是:static void * A :: operator new(size_t)

  4. 为什么编译器仍然喜欢  operator delete (void*),虽然已使用A::operator new(size_t, A*)  获得p2?

3 个答案:

答案 0 :(得分:2)

关于你的第一个和第三个问题,IMO就是这样的:

  1. 因为你没有使用:: new,编译器会尝试在A类中找到一个新的运算符。
  2. 找到一个,但找不到合适的过载,所以失败了。
  3. 如果您明确说明::new,则不应该有任何问题。如果编译器无法在类中找到新运算符的专用版本,则它只会转到全局命名空间。

    从标准:§5.3.4,

      

    9。如果new-expression以一个一元的::运算符开头,则在该表达式中查找分配函数的名称。   全球范围。否则,如果分配的类型是类类型T或其数组,则为分配函数   在T的范围内查找名称。如果此查找未能找到名称,或者分配的类型不是   类类型,在全局范围内查找分配函数的名称。

答案 1 :(得分:2)

让我们想象一些场景。首先,以下总是有效:

A * p1 = ::new A;
::delete p1;

A * p2 = ::new (addr) A;        // assume "void * addr" is valid
p2->~A();

全局表达式总是使用全局命名空间中的相应运算符,所以我们没问题。请注意,没有“placement-delete”表达式。必须通过调用析构函数来明确销毁每个放置构造的对象

接下来,假设我们写道:

A * p3 = new A;
delete p3;

这一次,首先在operator new(size_t)的范围中查找分配函数A。该名称存在,但如果删除正确的重载,则会出错。 (这回答了Q1和Q3。)同样适用于operator delete()。单参数版本((void *))优于双参数版本((void *, size_t))没有特别的原因;你应该只有两个中的一个。 (另请注意,双参数函数没有全局版本。)

最后,让我们重温一下展示新表达式。由于没有放置 - 删除表达式,您的最后一行代码是错误(未定义的行为):您不能delete任何未通过默认值获得的内容 - {{ 1}}表达。如果定义了placement-new分配函数,则还应定义匹配的释放函数。但是,只有在某个特定情况下才会自动调用此函数:如果placement-new expression 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 不执行任何操作。也就是说,全球布局新需要零清理。

答案 2 :(得分:0)

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