operator delete, operator delete[]

来自cppreference.com
< cpp‎ | memory‎ | new
 
 
工具库
通用工具
日期和时间
函数对象
格式化库 (C++20)
(C++11)
关系运算符 (C++20 中弃用)
整数比较函数
(C++20)(C++20)(C++20)
(C++20)
swap 与类型运算
(C++14)
(C++11)
(C++11)
(C++11)
(C++17)
常用词汇类型
(C++11)
(C++17)
(C++17)
(C++17)
(C++11)
(C++17)
(C++23)
初等字符串转换
(C++17)
(C++17)
 
动态内存管理
未初始化内存算法
受约束的未初始化内存算法
分配器
垃圾收集支持
(C++11)(C++23 前)
(C++11)(C++23 前)
(C++11)(C++23 前)
(C++11)(C++23 前)
(C++11)(C++23 前)
(C++11)(C++23 前)



 
低层内存管理
函数
operator deleteoperator delete[]
类型
对象
对象访问
(C++17)
 
在标头 <new> 定义
可替换的通常解分配函数
(1)
void operator delete  ( void* ptr ) throw();
(C++11 前)
void operator delete  ( void* ptr ) noexcept;
(C++11 起)
(2)
void operator delete[]( void* ptr ) throw();
(C++11 前)
void operator delete[]( void* ptr ) noexcept;
(C++11 起)
void operator delete  ( void* ptr, std::align_val_t al ) noexcept;
(3) (C++17 起)
void operator delete[]( void* ptr, std::align_val_t al ) noexcept;
(4) (C++17 起)
void operator delete  ( void* ptr, std::size_t sz ) noexcept;
(5) (C++14 起)
void operator delete[]( void* ptr, std::size_t sz ) noexcept;
(6) (C++14 起)
void operator delete  ( void* ptr, std::size_t sz,
                        std::align_val_t al ) noexcept;
(7) (C++17 起)
void operator delete[]( void* ptr, std::size_t sz,
                        std::align_val_t al ) noexcept;
(8) (C++17 起)
可替换的布置解分配函数
(9)
void operator delete  ( void* ptr, const std::nothrow_t& tag ) throw();
(C++11 前)
void operator delete  ( void* ptr, const std::nothrow_t& tag ) noexcept;
(C++11 起)
(10)
void operator delete[]( void* ptr, const std::nothrow_t& tag ) throw();
(C++11 前)
void operator delete[]( void* ptr, const std::nothrow_t& tag ) noexcept;
(C++11 起)
void operator delete  ( void* ptr, std::align_val_t al,
                        const std::nothrow_t& tag ) noexcept;
(11) (C++17 起)
void operator delete[]( void* ptr, std::align_val_t al,
                        const std::nothrow_t& tag ) noexcept;
(12) (C++17 起)
不分配布置解分配函数
(13)
void operator delete  ( void* ptr, void* place ) throw();
(C++11 前)
void operator delete  ( void* ptr, void* place ) noexcept;
(C++11 起)
(14)
void operator delete[]( void* ptr, void* place ) throw();
(C++11 前)
void operator delete[]( void* ptr, void* place ) noexcept;
(C++11 起)
用户定义的布置解分配函数
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)
类特定销毁解分配函数
void T::operator delete( T* ptr, std::destroying_delete_t);
(27) (C++20 起)
void T::operator delete( T* ptr, std::destroying_delete_t,
                         std::align_val_t al );
(28) (C++20 起)
void T::operator delete( T* ptr, std::destroying_delete_t, std::size_t sz );
(29) (C++20 起)
void T::operator delete( T* ptr, std::destroying_delete_t,
                         std::size_t sz, std::align_val_t al );
(30) (C++20 起)

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

1)delete 表达式调用,以解分配先前为单个对象分配的存储。此函数的标准库实现的行为只有在 ptr 是空指针或是先前从 operator new(std::size_t)operator new(std::size_t, std::nothrow_t) 的标准库实现获得的指针时才有定义。
2)delete[] 表达式调用,以解分配先前为对象数组分配的存储。此函数的标准库实现的行为只有在 ptr 是空指针或是先前从 operator new[](std::size_t)operator new[](std::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) 有定义且在解分配 T 类型对象时会被通常单对象 delete 表达式调用。
18) 有定义且在分配 T 类型对象的数组时会被通常数组 delete[] 表达式调用。
19,20) 有定义且对齐要求超出 __STDCPP_DEFAULT_NEW_ALIGNMENT__ 时会优先于 (17,18) 被调用。
21) 自身有定义但未定义 (17),且在解分配一个 T 类型对象时会被通常单对象 delete 表达式调用。
22) 自身有定义但未定义 (18),且在解分配 T 类型对象的数组时会被通常数组 delete[] 表达式调用。
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 是空指针,那么标准库解分配函数不做任何事。如果传递给标准库解分配函数的指针不是从对应的标准库分配函数取得的指针,那么行为未定义。

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

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

参数

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

返回值

(无)

异常

所有解分配函数均为 noexcept(true),除非在声明中另外说明。

(C++11 起)

如果解分配函数以抛异常终止,那么行为未定义,即使它以 noexcept(false) 声明 (C++11 起)

全局替换

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

对于某个可以替换的解分配函数,如果程序中提供了它的多个替换,或它有带 inline 说明符的替换声明,那么程序非良构,不要求诊断。如果替换在全局命名空间以外的命名空间中定义,或它被定义成在全局作用域的静态非成员函数,那么程序非良构。

nothrow 版本 (9,10) 的标准库实现直接调用抛出版本 (1,2)。具大小解分配函数 (5-8) 的标准库实现直接调用对应的不具大小解分配函数 (1-4)。不具大小的抛出数组形式 (2,4) 的标准库实现直接调用对应的单对象形式 (1,3)。因此只需要替换抛出单对象解分配函数 (1,3) 就可以处理所有解分配。

#include <cstdio>
#include <cstdlib>
#include <new>
 
// 最小函数集的替换:
 
// 无 inline ,由 [replacement.functions]/3 要求
void* operator new(std::size_t sz)
{
    std::printf("已调用全局 new 运算符,大小为 %zu\n", sz);
    if (sz == 0)
        ++sz; // 避免 std::malloc(0),它可能会在成功时返回 nullptr
 
    if (void *ptr = std::malloc(sz))
        return ptr;
 
    throw std::bad_alloc{}; // 由 [new.delete.single]/3 要求
}
 
void operator delete(void* ptr) noexcept
{
    std::puts("已调用全局 delete 运算符");
    std::free(ptr);
}
 
int main()
{
    int* p1 = new int;
    delete p1;
 
    int* p2 = new int[10]; // C++11 中保证调用替换
    delete[] p2;
}

可能的输出:

已调用全局 new 运算符,大小为 4
已调用全局 delete 运算符
已调用全局 new 运算符,大小为 40
已调用全局 delete 运算符

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 << "已调用自定义布置 new,b = " << b << '\n';
        return ::operator new(sz);
    }
 
    // 自定义布置 delete
    static void operator delete(void* ptr, bool b)
    {
        std::cout << "已调用自定义布置 delete,b = " << b << '\n';
        ::operator delete(ptr);
    }
};
 
int main()
{
    try
    {
        X* p1 = new (true) X;
    }
    catch(const std::exception&) {}
}

输出:

已调用自定义布置 new,b = 1
已调用自定义布置 delete,b = 1

如果类级别的 operator delete 是模板函数,那么它的返回类型必须是 void,首参数必须是 void*,而且必须拥有两个或更多参数。换言之,只有布置形式可以是模板。模板 operator delete 的特化由模板参数推导选择。

注解

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

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

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

(C++11 起)

缺陷报告

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

缺陷报告 应用于 出版时的行为 正确行为
CWG 220 C++98 允许用户定义的解分配函数抛出异常 从解分配函数抛出异常会导致未定义行为
CWG 1438 C++98 任何非法指针值的使用都是未定义行为 只有间接和解分配是
LWG 206 C++98 替换掉 (2) 不会影响 (10) 的默认行为 默认行为也会有相应变更
LWG 298 C++98 替换掉 (1) 不会影响 (9) 的默认行为 默认行为也会有相应变更
LWG 404 C++98 可替换的解分配函数在替换时可以声明为 inline 已禁止,不要求诊断
LWG 2458 C++14 有一个接受 (void*, std::size_t, const
std::nothrow_t&) 的重载,但它无法被调用
移除虚假重载

参阅

分配函数
(函数)
(C++17 中弃用)(C++20 中移除)
释放未初始化存储
(函数模板)
解分配之前分配的内存
(函数)