operator delete, operator delete[]
| 在标头 <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、delete[] 和布置 new 表达式调用,以在析构(或构造失败)拥有动态存储期的对象后解分配内存。它们也可以用常规函数调用语法调用。
delete 表达式调用。使所有非空 ptr 失效。- 如果
ptr不是空指针,并且满足以下任一条件,那么行为未定义:- 对于
operator delete,ptr的值表示的不是由先前的某次对(可能被替换的)operator new(std::size_t)(对于重载 (1,5,9))或operator new(std::size_t, std::align_val_t)(对于重载 (3,7,11))调用分配的且尚未因其他operator delete调用而失效的内存块。 - 对于
operator delete[],ptr的值表示的不是由先前的某次对(可能被替换的)operator new[](std::size_t)(对于重载 (1,5,9))或operator new[](std::size_t, std::align_val_t)(对于重载 (3,7,11))调用分配的且尚未因其他operator delete[]调用而失效的内存块。
- 对于
delete、delete[] 和布置 new 表达式调用的用户定义解分配函数。delete 表达式在调用 operator delete 前不会对 *ptr 执行析构函数。析构函数改为由此 operator delete 负责直接调用,例如用 ptr->~T();。即使不包含标头 <new>,重载 (1-8) 也会在每个翻译单元隐式声明。
选择重载的标准见 delete 表达式。
参数
| ptr | - | 指向要解分配的内存块的指针或空指针 |
| sz | - | 传递给匹配的分配函数的大小 |
| place | - | 用作匹配的布置 new 中布置参数的指针 |
| tag | - | 匹配不抛出 operator new 所用标签的重载消歧义标签 |
| al | - | 被分配的对象或数组元素的对齐 |
| args | - | 匹配布置分配函数的任意参数(可包含 std::size_t 与 std::align_val_t) |
异常
|
所有解分配函数均为 |
(C++11 起) |
如果解分配函数以抛异常终止,那么行为未定义,即使它以 noexcept(false) 声明(C++11 起)。
全局替换
ptr 为空,那么什么也不做。否则回收先前 operator new 调用分配的存储。operator delete(ptr),此时重载 (1) 如同可以回收先前 operator new[] 调用分配的存储。operator delete(ptr, al),此时重载 (3) 如同可以回收先前 operator new[] 调用分配的存储。operator delete(ptr)。operator delete[](ptr)。operator delete(ptr, al)。operator delete[](ptr, al)。operator delete(ptr)。operator delete[](ptr)。operator delete(ptr, al)。operator delete[](ptr, al)。替换全局 operator new/delete:
#include <cstdio>
#include <cstdlib>
#include <new>
// 无 inline,由 [replacement.functions]/3 要求
void* operator new(std::size_t sz)
{
std::printf("1) new(size_t), size = %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 要求
}
// 无 inline,由 [replacement.functions]/3 要求
void* operator new[](std::size_t sz)
{
std::printf("2) new[](size_t), size = %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("3) delete(void*)");
std::free(ptr);
}
void operator delete(void* ptr, std::size_t size) noexcept
{
std::printf("4) delete(void*, size_t), size = %zu\n", size);
std::free(ptr);
}
void operator delete[](void* ptr) noexcept
{
std::puts("5) delete[](void* ptr)");
std::free(ptr);
}
void operator delete[](void* ptr, std::size_t size) noexcept
{
std::printf("6) delete[](void*, size_t), size = %zu\n", size);
std::free(ptr);
}
int main()
{
int* p1 = new int;
delete p1;
int* p2 = new int[10]; // C++11 中保证调用替换
delete[] p2;
}
可能的输出:
// 以 GCC-5 的 C++17 模式编译,可获得如下输出:
1) op new(size_t), size = 4
4) op delete(void*, size_t), size = 4
2) op new[](size_t), size = 40
5) op delete[](void* ptr)
operator delete 与 operator delete[] 带额外用户定义形参的重载(“布置形式”,(15,16))可照常在全局作用域声明,而且会被匹配的布置形式 new 表达式在所分配的对象的构造函数抛出异常时调用。
operator delete 与 operator delete[] 的标准库布置形式 (13,14) 不能替换,而且只有在布置 {{c/core|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 <cstddef>
#include <iostream>
// 指定大小的类特定解分配函数
struct X
{
static void operator delete(void* ptr, std::size_t sz)
{
std::cout << "自定义 delete,大小是 " << sz << '\n';
::operator delete(ptr);
}
static void operator delete[](void* ptr, std::size_t sz)
{
std::cout << "自定义 delete,大小是 " << sz << '\n';
::operator delete(ptr);
}
};
int main()
{
X* p1 = new X;
delete p1;
X* p2 = new X[10];
delete[] p2;
}可能的输出:
自定义 delete,大小是 1
自定义 delete,大小是 18带额外用户定义形参的 operator delete 与 operator delete[](“布置形式”,(25,26))也可以定义为类成员。在失败的布置 new 表达式查找将调用的对应布置 delete 函数时,它首次从类作用域开始,在全局作用域之前查找,并且查找匹配布置 new 签名的函数:
#include <cstddef>
#include <iostream>
#include <stdexcept>
struct X
{
X() { throw std::runtime_error("X(): 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
{
[[maybe_unused]] X* p1 = new (true) X;
}
catch (const std::exception& ex)
{
std::cout << ex.what() << '\n';
}
}输出:
已调用自定义布置 new,b = 1
已调用自定义布置 delete,b = 1
X(): std::runtime_error如果类级别的 operator delete 是模板函数,那么它的返回类型必须是 void,首个实参必须是 void*,而且必须拥有两个或更多形参。换言之,只有布置形式可以是模板。模板实例始终不是常规解分配函数,无论其签名如何。模板 operator delete 的特化由模板实参推导选择。
注解
在多态类上调用类特定的 T::operator delete 是仅有的通过动态派发调用静态成员函数的情况。
|
要求下列函数是线程安全的:
对这些分配或解分配特定存储单元的函数调用以单独全序出现,而并且在此顺序中,每个解分配调用先发生于下个分配(如果存在)。 |
(C++11 起) |
| 功能特性测试宏 | 值 | 标准 | 功能特性 |
|---|---|---|---|
__cpp_sized_deallocation |
201309L |
(C++14) | 具大小解分配 |
__cpp_impl_destroying_delete |
201806L |
(C++20) | 销毁 operator delete (编译器支持) |
__cpp_lib_destroying_delete |
201806L |
(C++20) | 销毁 operator delete (库支持) |
缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 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, conststd::nothrow_t&) 的重载,但它无法被调用
|
移除虚假重载 |
参阅
[静态] (C++23) |
解分配先前从 operator new 获得的内存 ( std::generator<Ref,V,Allocator>::promise_type 的公开静态成员函数)
|
| 分配函数 (函数) | |
(C++17 弃用)(C++20 移除) |
释放未初始化存储 (函数模板) |
| 解分配之前分配的内存 (函数) |