operator delete, operator delete[]

来自cppreference.com
< cpp‎ | memory‎ | new
 
 
工具库
类型支持(基本类型、 RTTI 、类型特性)
动态内存管理
错误处理
程序工具
变参数函数
日期和时间
函数对象
(C++11)
关系运算符
optionalanyvariant (C++17)
pair 与 tuple
(C++11)
(C++17)
swap 、 forward 与 move
(C++14)
(C++11)
(C++11)
初等字符串转换
(C++17)
(C++17)
类型运算
(C++11)
(C++17)
 
 
 
定义于头文件 <new>
可替换通常解分配函数
void operator delete  ( void* ptr );
(1)
void operator delete[]( void* ptr );
(2)
void operator delete  ( void* ptr, std::align_val_t al );
(3) (C++17 起)
void operator delete[]( void* ptr, std::align_val_t al );
(4) (C++17 起)
void operator delete  ( void* ptr, std::size_t sz );
(5) (C++14 起)
void operator delete[]( void* ptr, std::size_t sz );
(6) (C++14 起)
void operator delete  ( void* ptr, std::size_t sz, std::align_val_t al );
(7) (C++17 起)
void operator delete[]( void* ptr, std::size_t sz, std::align_val_t al );
(8) (C++17 起)
可替换布置解分配函数
void operator delete  ( void* ptr, const std::nothrow_t& tag );
(9)
void operator delete[]( void* ptr, const std::nothrow_t& tag );
(10)
void operator delete  ( void* ptr,
                        std::align_val_t al, const std::nothrow_t& tag );
(11) (C++17 起)
void operator delete[]( void* ptr,
                        std::align_val_t al, const std::nothrow_t& tag );
(12) (C++17 起)
不分配布置解分配函数
void operator delete  ( void* ptr, void* place );
(13)
void operator delete[]( void* ptr, void* place );
(14)
用户定义布置解分配函数
void operator delete  ( void* ptr, args... );
(15)
void operator delete[]( void* ptr, args... );
(16)
类指定通常解分配函数
void T::operator delete  ( void* ptr );
(17)
void T::operator delete[]( void* ptr );
(18)
void T::operator delete  ( void* ptr, std::align_val_t al );
(19) (C++17 起)
void T::operator delete[]( void* ptr, std::align_val_t al );
(20) (C++17 起)
void T::operator delete  ( void* ptr, std::size_t sz );
(21)
void T::operator delete[]( void* ptr, std::size_t sz );
(22)
void T::operator delete  ( void* ptr, std::size_t sz, std::align_val_t al );
(23) (C++17 起)
void T::operator delete[]( void* ptr, std::size_t sz, std::align_val_t al );
(24) (C++17 起)
类指定布置解分配函数
void T::operator delete  ( void* ptr, args... );
(25)
void T::operator delete[]( void* ptr, args... );
(26)

解分配先前由匹配的 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) or operator new[](size_t, std::nothrow_t) 的标准库实现获得的指针。
3.4)(1,2) ,除了若对齐要求超出 __STDCPP_DEFAULT_NEW_ALIGNMENT__ 才被调用
5-6) 若提供用户定义重载,则取代 (1-2) 得到调用,除了在删除不完整类型的对象和非类与可平凡析构类类型的数组时,调用 (1-2) 还是 (5-6) 是实现定义的 (C++17 起)。内存分配器可用给定的大小变得更高效。标准库实现等同于 (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) ,则不调用析构函数。

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

(C++17 起)

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

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

已因此举动变为非法的指针的任何使用,即使是复制指针值到另一指针变量,都是未定义行为。

(C++14 前)

通过已因此举动变为非法的指针解引用,并将它传递给解分配函数(双重 delete )是未定义行为。任何其他使用是实现定义的。

(C++14 起)

目录

[编辑] 参数

ptr - 指向要解分配的内存块的指针或空指针
sz - 传递给匹配的分配函数的 size
place - 用作匹配的布置 new 中布置参数的指针
tag - 匹配不抛出 operator new 所用标签的重载消歧义标签
al - 被分配的对象或数组元素的对齐
args - 匹配布置分配函数的任意参数(可包含 std::size_tstd::align_val_t

[编辑] 返回值

(无)

[编辑] 异常

(无) (C++11 前)
noexcept 规定:  
noexcept
  
(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*)

所有解分配函数均为 noexcept(true) ,除非在声明中指定为其他情况。 (C++11 起)

[编辑] 类指定重载

节分配函数 (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 是仅有的通过动态派发调用静态成员函数的情况。

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

  • operator newoperator delete 的库版本
  • 全局 operator newoperator delete 的用户替换版本
  • std::callocstd::mallocstd::reallocstd::aligned_alloc (C++17 起)

对这些分配或解分配特定存储单元的函数调用以单独全序出现,并且在此顺序中,每个解分配调用先发生于下个分配(若存在)。

(C++11 起)

[编辑] 缺陷报告

下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。

DR 应用于 出版时的行为 正确行为
LWG 2458 C++14 曾指定了接受 (void*,size_t,const nothrow_t&) 的重载,但它无法被调用 移除虚假重载

[编辑] 参阅

分配函数
(函数) [编辑]
释放未初始化存储
(函数)
解分配内存
(函数)