【问题标题】:Questions about operator new() and operator delete()关于 operator new() 和 operator delete() 的问题
【发布时间】:2012-01-03 10:35:18
【问题描述】:

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

/*
* 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 整数)’ 注意:候选对象是: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?

【问题讨论】:

  • 这是很多问题。如果你想亲自看看n3242.pdf,我会从5.3.4 New开始(第110页)
  • 无论如何我认为所有的问题都与同一个主题有关。
  • 如果可能只有一个答案可以解决整个问题,那么 Q/A 格式的效果最好。当多个答案组合起来提供答案时,您会感到困惑的是要接受什么答案。这使得未来的 SO 搜索者很难/不可能找到信息。
  • 我很确定这不是来自生产代码,但您可能不想在operator new 中使用std::cout(即使用于调试),因为不能保证输出操作不会分配内存(并调用operator new())。
  • 出于这个原因,@Sehe,我已投票决定将其作为“不是一个真正的问题”来结束。对这个密切原因的解释提到“过于宽泛”。

标签: c++


【解决方案1】:

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

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

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

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

接下来,假设我们写:

A * p3 = new A;
delete p3;

这一次,分配函数operator new(size_t)首先在A的范围内查找。该名称存在,但如果删除正确的重载,则会出现错误。 (这回答了 Q1 和 Q3。)operator delete() 也是如此。单参数版本 ((void *)) 优于双参数版本 ((void *, size_t)) 并没有什么特别的原因;你应该只拥有两者之一。 (另请注意,没有 global 版本的双参数函数。)

最后,让我们重温一下placement-new 表达式。由于没有放置删除表达式,你的最后一行代码是一个错误(未定义的行为):你不能delete任何不是通过默认-new表达式获得的东西.如果你定义了一个placement-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 *) 什么都不做。即全局placement-new需要零清理。

【讨论】:

    【解决方案2】:

    至于您的第一个和第三个问题,IMO 的工作方式是这样的:

    1. 因为您没有使用 ::new,所以编译器会尝试在 A 类中查找 new 运算符。
    2. 它找到了一个,但它找不到合适的重载,所以它失败了。

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

    来自标准:§ 5.3.4,

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

    【讨论】:

    • "如果此查找找不到名称,或者分配的类型不是类类型,则在全局范围内查找分配函数的名称。"
    • @Zack:这就是答案。请注意,标准在这里提到了 name 查找 - 重载的候选者解析是另一回事。可以在那里找到new 的一些合法定义(这是 g++ 打印的定义),因此不会触发规则。当 A 类中没有 new 函数时触发规则,可以通过注释第二个定义来观察。
    【解决方案3】:

    一个对象不记得它是如何创建的;仅当对应的placement new throws 时才使用placement delete,否则使用常规delete 运算符。

    【讨论】:

      猜你喜欢
      • 2021-10-11
      • 1970-01-01
      • 1970-01-01
      • 2021-07-28
      • 2011-04-07
      • 2011-08-30
      • 1970-01-01
      • 1970-01-01
      • 2020-01-12
      相关资源
      最近更新 更多