添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

解分配先前由匹配的 operator new 所分配的存储。这些解分配函数为 delete 表达式 new 表达式 所调用,以在析构(或构造失败)拥有动态存储期的对象后解分配内存。它们亦可用常规函数调用语法调用。

1) delete 表达式 所调用,以解分配先前为单对象分配的存储。此函数的标准库实现的行为未定义,除非 ptr 是空指针或是先前从 operator new ( size_t ) operator new ( size_t, std:: nothrow_t ) 的标准库实现获得的指针。
2) delete[] 表达式 所调用,以解分配先前为对象数组分配的存储。此函数的标准库实现的行为未定义,除非 ptr 是空指针或是先前从 operator new [ ] ( size_t ) operator new [ ] ( size_t, std:: nothrow_t ) 的标准库实现获得的指针。
3,4) (1,2) ,除了若对齐要求超出 __STDCPP_DEFAULT_NEW_ALIGNMENT__ 才被调用
5,6) 若提供用户定义重载,则取代 (1-2) 得到调用,除了在删除不完整类型的对象、非类类型的数组和可平凡析构类类型的数组时,调用 (1-2) 还是 (5-6) 是未指定的。内存分配器可用给定的大小变得更高效。标准库实现等同于 (1-2)
7,8) (5-6) ,除了若对齐要求超出 __STDCPP_DEFAULT_NEW_ALIGNMENT__ 才被调用
9) 若对象的构造函数抛出异常,则为不抛出的单对象 new 表达式 所调用。标准库实现表现同 (1)
10) 若任何对象的构造函数抛出异常,则(在执行数组中已成功构造的所有对象的析构函数后)为不抛出的数组 new[] 表达式 所调用。标准库实现表现同 (2)
11,12) (9,10) ,除了若对齐要求超出 __STDCPP_DEFAULT_NEW_ALIGNMENT__ 才被调用
13) 若对象的构造函数抛出异常,则为标准单对象 布置 new 表达式所调用。此函数的标准库实现不做任何事。
14) 若任何对象的构造函数抛出异常,则为 布置 new 表达式的数组形式(在执行数组中已成功构造的所有对象的析构函数后)所调用。此函数的标准库实现不做任何事。
15) 若定义,则为拥有匹配签名的自定义单对象 布置 new 表达式调用,若对象构造函数抛出异常。若定义类特定版本 (25) ,则类特定版本优先于 (9) 调用。若用户既不提供 (25) 又不提供 (15) ,则不调用解分配函数。
16) 若定义,则为拥有匹配签名的 布置 new[] 表达式自定义数组形式(在执行数组中已成功构造的所有对象的析构函数后)调用,若任何对象的构造函数抛出异常。若定义类特定版本 (16) ,则类特定版本优先于 (10) 调用。若用户既不提供 (26) 又不提供 (16) ,则不调用解分配函数。
17) 若定义,则为通常单对象 delete 表达式 调用,若在解分配一个 T 类型对象。
18) 若定义,则为通常数组 delete[] 表达式 调用,若在解分配 T 类型对象的数组。
19,20) 若定义,则优先于 (17,18) 调用,若对齐要求超出 __STDCPP_DEFAULT_NEW_ALIGNMENT__
21) 若定义,且若未定义 (17) ,则为通常单对象 delete 表达式 调用,若在解分配一个 T 类型对象。
22) 若定义,且若未定义 (18) ,则为通常数组 delete[] 表达式 调用,若在解分配 T 类型对象的数组。
23,24) 若定义,且若未定义 (19,20) ,则优先于无对齐成员调用,若对齐要求超出 __STDCPP_DEFAULT_NEW_ALIGNMENT__ .
25) 若定义,则为拥有匹配签名的自定义单对象 布置 new 表达式调用,若对象构造函数抛出异常。若不提供此函数,亦不提供匹配的 (15) ,则不调用解分配函数。
26) 若定义,则为拥有匹配签名的自定义 布置 new[] 表达式的数组形式(在执行数组中已成功构造的所有对象的析构函数后)调用,若任何对象的构造函数抛出异常。若不提供此函数,亦不提供匹配的 (16) ,则不调用析构函数。
27-30) 若定义,则 delete 表达式 在调用 operator delete 前不对 *p 执行析构函数。取而代之地,变为由此用户定义的 operator delete 负责直接调用析构函数,例如用 p - > ~T ( ) ;

通常(非布置)解分配函数的具对齐和不具对齐重载之间的重载决议准确细节见 delete 表达式 (C++17 起)

所有情况下,若 ptr 是空指针,则标准库解分配函数不做任何事。若传递给标准库解分配函数的指针不是从对应的标准库分配函数取得者,则行为未定义。

在标准库解分配函数返回后,所有引用到被解分配存储的任何部分的指针都变为非法。

已因此方式变为非法的指针的任何使用,即使是复制指针值到另一指针变量,都是未定义行为。 (C++14 前)

通过已因此方式变为非法的指针解引用,以及将它传递给解分配函数(双重 delete )是未定义行为。任何其他使用是实现定义的。 (C++14 起) (C++11 前)

15-30) 所有解分配函数均为 noexcept(true) ,除非在声明中另外说明。若解分配函数以抛异常终止,则行为未定义,即使以 noexcept(false) 声明它。 (C++11 起)

可替换解分配函数 (1-10) 在每个翻译单元隐式声明,即使不包含 <new> 头文件。这些函数是 可替换 的:定义于程序任何位置、任何头文件的用户提供的拥有相同签名的非成员函数,会为整个程序替换对应的隐式版本。其声明不必可见。

若程序中提供多于一个替换,或若替换声明有 inline 指定符,则行为未定义,若替换声明于异于全局命名空间的命名空间,或若它定义为在全局作用域的 static 非成员函数,则程序为病态。

nothrow 版本 (9,10) 的标准库实现直接调用抛出版本 (1,2) 。具大小解分配函数 (5-8) 的标准库实现直接调用对应的不具大小解分配函数 (1-4) 。不具大小的抛出数组形式 (2,4) 的标准库实现直接调用对应的单对象形式 (1,3)

故而,替换抛出的单对象解分配函数 (1,3) 足以处理所有解分配。 (C++11 起)

#include <cstdio>
#include <cstdlib>
// 函数最小集的替换:
void* operator new(std::size_t sz) {
    std::printf("global op new called, size = %zu\n",sz);
    return std::malloc(sz);
void operator delete(void* ptr) noexcept
    std::puts("global op delete called");
    std::free(ptr);
int main() {
     int* p1 = new int;
     delete p1;
     int* p2 = new int[10]; // C++11 中保证调用替换者
     delete[] p2;

可能的输出:

global op new called, size = 4
global op delete called
global op new called, size = 40
global op delete called

operator deleteoperator delete[] 带额外用户定义参数的重载(“布置形式”, (15,16) )可照常声明于全局作用域,而且为匹配的布置形式 new 表达式 所调用,若正在分配的对象的构造函数抛出异常。

operator delete 的标准库布置形式 (13,14) 不能替换,而且仅若布置 new 表达式不使用 ::new 语法才能被自定义,通过提供拥有匹配签名的类特定布置 delete (25,26)void T::operator delete(void*, void*)void T::operator delete[](void*, void*)

类特定重载

节分配函数 (17-24) 可定义为类的静态成员函数。若提供这些解分配函数,则它们为 delete 表达式在删除此类的对象 (17,19,21) 和数组 (18,20,22) 时调用,除非 delete 表达式使用跳过类作用域查找的形式 ::delete 。关键词 static 对这些函数声明是可选的:不管是否使用该关键词,解分配函数都始终是静态成员函数。

delete 表达式从类作用域开始查找适当的解分配函数名(数组形式在数组元素类的作用域查找),然后若找不到成员再继而照常寻找全局作用域。注意,同每个名称查找规则,任何声明于类作用域的解分配函数隐藏所有全局解分配函数。

若正在删除的对象的静态类型异于其动态类型(例如通过指向基类的指针删除一个多态对象),且若静态类型中的析构函数是虚的,则 delete 的单对象形式从其虚析构函数的最终覆盖者的定义点开始查找解分配函数的名称。无关乎运行时会执行哪个析构函数,为了能够编译, operator delete 的静态可见版本必须可访问。其他情况下,当通过指向基类的指针删除数组,或通过带非虚析构函数的指针删除时,行为未定义。

若未提供单参数重载 (17,18) ,但提供接收 std::size_t 为第二参数的具大小重载 (21,22) ,则正常解分配调用具大小形式,且 C++ 运行时传递要被解分配的对象大小为第二参数。若定义两种形式,则调用不具大小的版本。

#include <iostream>
// 具大小的类特定解分配函数
struct X {
    static void operator delete(void* ptr, std::size_t sz)
        std::cout << "custom delete for size " << sz << '\n';
        ::operator delete(ptr);
    static void operator delete[](void* ptr, std::size_t sz)
        std::cout << "custom delete for size " << sz << '\n';
        ::operator delete(ptr);
int main() {
     X* p1 = new X;
     delete p1;
     X* p2 = new X[10];
     delete[] p2;

可能的输出:

custom delete for size 1
custom delete for size 18

带额外用户定义参数的 operator deleteoperator delete[] (“布置形式”, (25,26) )亦可定义为类成员。在失败的布置 new 表达式查找将调用的对应布置 delete 函数时,它首次从类作用域开始,在全局作用域之前查找,并且查找匹配布置 new 签名的函数:

#include <stdexcept>
#include <iostream>
struct X {
    X() { throw std::runtime_error(""); }
    // 自定义布置 new
    static void* operator new(std::size_t sz, bool b) {
        std::cout << "custom placement new called, b = " << b << '\n';
        return ::operator new(sz);
    // 自定义布置 delete
    static void operator delete(void* ptr, bool b)
        std::cout << "custom placement delete called, b = " << b << '\n';
        ::operator delete(ptr);
int main() {
   try {
     X* p1 = new (true) X;
   } catch(const std::exception&) { }
custom placement new called, b = 1
custom placement delete called, b = 1

若类级别的 operator delete 是模板函数,则它必须拥有 void 返回类型,首参数 void* ,而且必须拥有二个或更多参数。换言之,只有布置形式能是模板。模板 operator delete 的特化为模板参数推导所选择。

在多态类上调用类指定的 T::operator delete 是仅有的通过动态派发调用静态成员函数的情况。

要求下列函数是线程安全的: