operator new, operator new[]
在标头 <new> 定义
|
||||||
可替换的分配函数
|
||||||
void* operator new ( std::size_t count ); |
(1) | |||||
void* operator new[]( std::size_t count ); |
(2) | |||||
void* operator new ( std::size_t count, std::align_val_t al ); |
(3) | (C++17 起) | ||||
void* operator new[]( std::size_t count, std::align_val_t al ); |
(4) | (C++17 起) | ||||
可替换的不抛出分配函数
|
||||||
void* operator new ( std::size_t count, const std::nothrow_t& tag ); |
(5) | |||||
void* operator new[]( std::size_t count, const std::nothrow_t& tag ); |
(6) | |||||
void* operator new ( std::size_t count, std::align_val_t al, const std::nothrow_t& ); |
(7) | (C++17 起) | ||||
void* operator new[]( std::size_t count, std::align_val_t al, const std::nothrow_t& ); |
(8) | (C++17 起) | ||||
不分配布置分配函数
|
||||||
void* operator new ( std::size_t count, void* ptr ); |
(9) | |||||
void* operator new[]( std::size_t count, void* ptr ); |
(10) | |||||
用户定义布置分配函数 |
||||||
void* operator new ( std::size_t count, 用户定义实参... ); |
(11) | |||||
void* operator new[]( std::size_t count, 用户定义实参... ); |
(12) | |||||
void* operator new ( std::size_t count, std::align_val_t al, 用户定义实参... ); |
(13) | (C++17 起) | ||||
void* operator new[]( std::size_t count, std::align_val_t al, 用户定义实参... ); |
(14) | (C++17 起) | ||||
类特定分配函数 |
||||||
void* T::operator new ( std::size_t count ); |
(15) | |||||
void* T::operator new[]( std::size_t count ); |
(16) | |||||
void* T::operator new ( std::size_t count, std::align_val_t al ); |
(17) | (C++17 起) | ||||
void* T::operator new[]( std::size_t count, std::align_val_t al ); |
(18) | (C++17 起) | ||||
类特定布置分配函数 |
||||||
void* T::operator new ( std::size_t count, 用户定义实参... ); |
(19) | |||||
void* T::operator new[]( std::size_t count, 用户定义实参... ); |
(20) | |||||
void* T::operator new ( std::size_t count, std::align_val_t al, 用户定义实参... ); |
(21) | (C++17 起) | ||||
void* T::operator new[]( std::size_t count, std::align_val_t al, 用户定义实参... ); |
(22) | (C++17 起) | ||||
尝试分配请求数量的字节,而且分配请求可能会失败(即使请求分配的字节数为零)。这些分配函数会被 new 表达式调用,以分配将被初始化的对象所在的内存。它们也可以通过常规函数调用语法调用。
__STDCPP_DEFAULT_NEW_ALIGNMENT__
时会被通常的单对象 new 表达式调用。如果不提供此重载但提供了无对齐成员形式 (15),那么就会改为调用无对齐成员重载。__STDCPP_DEFAULT_NEW_ALIGNMENT__
时会被通常的数组 new[] 表达式调用。如果不提供此重载但提供了无对齐成员形式 (16),那么就会改为调用无对齐成员重载。参数
count | - | 要分配的字节数 |
ptr | - | 指向要初始化的对象所在的内存区域的指针 |
tag | - | 用于选择不抛出重载的消歧义标签 |
al | - | 使用的对齐。若此非有效对齐值,则行为未定义 |
返回值
异常
全局替换
即使不包含标头 <new>,版本 (1-4) 也会在每个翻译单元隐式声明。版本 (1-8) 可以替换:在程序任意位置定义并在任意源文件的用户提供的拥有相同签名的非成员函数都会替换默认版本。它的声明不需要可见。
对于某个可以替换的分配函数,如果程序中提供了它的多个替换,或它有带 inline
说明符的替换声明,那么程序非良构,不要求诊断。如果替换在全局命名空间以外的命名空间中定义,或它被定义成在全局作用域的静态非成员函数,那么程序非良构。
不抛出版本的标准库实现 (5-8) 直接调用对应的抛出版本 (1-4)。抛出的数组版本的标准库实现 (2,4) 直接调用对应的单对象版本 (1,3)。因此只需要替换抛出单对象分配函数就可以处理所有分配。
在独立实现上,(1-8) 的默认版本的行为是否满足以上要求由实现定义。建议独立实现在其中有任何默认版本满足宿主实现的要求的情况下,其他所有版本都应同时满足。 |
(C++26 起) |
#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 new
和 operator new[]
带额外用户定义参数的重载(“布置形式”,版本 (11-14))可以照常在全局作用域声明,而且会被匹配的布置形式 new 表达式 调用。
标准库的 operator new
的不分配布置形式 (9,10) 不能被替换,而且只能在布置 new 表达式不使用 ::new 语法时才能通过提供类特定的带匹配签名的布置 new (19,20)自定义:void* T::operator new(size_t, void*) 或 void* T::operator new[](size_t, void*)。
不能使用布置形式 void* operator new(std::size_t, std::size_t),因为对应的解分配函数的匹配签名 void operator delete(void*, std::size_t) 是通常(非布置)的解分配函数。 |
(C++14 起) |
类特定重载
单对象和数组分配函数都可以定义为类的公开静态成员函数(版本 (15-18)。有定义时 new 表达式会调用这些函数,以分配此类单个对象或数组的内存,除非 new 表达式使用 ::new 形式,这样就会跳过类作用域查找。对这些函数不需要使用关键词 static:不管是否使用,分配函数都是静态成员函数。
new 表达式首先在类作用域中查找适当的函数名,然后在全局作用域查找。注意,与每个名称查找规则一样,就试图分配此类对象的 new 表达式而言,任何在类作用域声明的分配函数都会隐藏所有全局分配函数。
在分配对齐超出 __STDCPP_DEFAULT_NEW_ALIGNMENT__ 的对象或元素对齐超出 __STDCPP_DEFAULT_NEW_ALIGNMENT__ 的对象数组时,进行两次重载决议:首先,针对具对齐的函数签名,然后,针对无对齐的函数签名。这意味着如果某个拥有扩展对齐的类拥有针对无对齐的类特定分配函数,那么该函数,而非全局的具对齐分配函数将会被调用。这是有意的:类成员应该对如何处理该类有最好的理解。 |
(C++17 起) |
在分配对齐没有超出 __STDCPP_DEFAULT_NEW_ALIGNMENT__ 的对象或元素对齐没有超出 __STDCPP_DEFAULT_NEW_ALIGNMENT__ 的对象数组时,进行两次重载决议:首先,针对无对齐的函数签名,然后,针对具对齐的函数签名。 |
(C++20 起) |
#include <iostream> // 类特定分配函数 struct X { static void* operator new(std::size_t sz) { std::cout << "大小为 " << sz << " 的自定义 new\n"; return ::operator new(sz); } static void* operator new[](std::size_t sz) { std::cout << "大小为 " << sz << " 的自定义 new[]\n"; return ::operator new(sz); } }; int main() { X* p1 = new X; delete p1; X* p2 = new X[10]; delete[] p2; }
可能的输出:
大小为 1 的自定义 new 大小为 10 的自定义 new[]
operator new
和 operator new[]
带额外用户定义参数(“布置形式”)的重载也可以定义为类成员 (19-22)。在拥有匹配签名的布置 new 表达式查找要调用的对应分配函数时,查找在检验全局作用域前,从类作用域开始,且在提供了类特定的布置 new 时会调用它。
在分配对齐超出 __STDCPP_DEFAULT_NEW_ALIGNMENT__ 的对象或元素对齐超出 __STDCPP_DEFAULT_NEW_ALIGNMENT__ 的对象数组时,与通常形式一样对布置形式进行两次重载决议:首先,针对具对齐的函数签名,然后,针对无对齐的函数签名。 |
(C++17 起) |
在分配对齐没有超出 __STDCPP_DEFAULT_NEW_ALIGNMENT__ 的对象或元素对齐没有超出 __STDCPP_DEFAULT_NEW_ALIGNMENT__ 的对象数组时,与通常形式一样对布置形式进行两次重载决议:首先,针对无对齐的函数签名,然后,针对具对齐的函数签名。 |
(C++20 起) |
#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 new
是模板函数,那么它必须拥有返回类型 void*,第一参数类型 std::size_t,且它必须拥有两个或更多参数。换言之,只有布置形式可以是模板。
注解
尽管不分配布置 new (9,10) 不能被替换,但在上面描述的类作用域也可以定义拥有相同签名的函数。另外,可以使用像是布置 new 但接受非 void 指针类型作为第二参数的全局重载,所以希望确保调用真正的布置 new 的代码(例如 std::allocator::construct)必须使用 ::new 并将指针转型到 void*。
要求下列函数是线程安全的:
对这些分配或解分配特定存储单元的函数调用以单独全序出现,而并且在此顺序中,每个解分配调用先发生于下个分配(如果存在)。 |
(C++11 起) |
operator new
的库版本是否产生任何对 std::malloc 或 std::aligned_alloc (C++17 起) 的调用是未指定的。
对于加载大文件,经由如 POSIX 上的 mmap
或 Windows 上的 CreateFileMapping
(A
/W
) 伴随 MapViewOfFile
的操作系统特定函数进行文件映射,比为文件读取分配缓冲区更适合。
功能特性测试宏 | 值 | 标准 | 注释 |
---|---|---|---|
__cpp_lib_freestanding_operator_new |
202306L | (C++26) | 可替换的 operator new 的独立支持[1] |
0 | (C++26) | 无独立支持 |
- ↑ 正式而言,此宏会在所有可替换的全局分配函数的默认版本都满足宿主实现的要求的情况下展开为 202306L。
缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
缺陷报告 | 应用于 | 出版时的行为 | 正确行为 |
---|---|---|---|
CWG 521 | C++98 | 任何从 std::bad_alloc 派生的类都可以抛出, 即使该 std::bad_alloc 基有歧义或不可访问 |
抛出的异常应与类型是 std::bad_alloc 的处理块匹配 |
LWG 9 | C++98 | 多次请求分配零字节的调用可以产生相同的指针 | 只有在之前产生的这些指针都 被传递给解分配函数时才可以 |
LWG 206 | C++98 | 替换掉可替换的分配函数不会影响对应的可替换的不抛出分配函数的默认行为 | 默认行为也会有相应变更 |
LWG 404 | C++98 | 可替换的分配函数在替换时可以声明为 inline | 已禁止,不要求诊断 |
引用
- C++20 标准(ISO/IEC 14882:2020):
- 17.6 Dynamic memory management [support.dynamic]
- C++17 标准(ISO/IEC 14882:2017):
- 21.6 Dynamic memory management [support.dynamic]
- C++14 标准(ISO/IEC 14882:2014):
- 18.6 Dynamic memory management [support.dynamic]
- C++11 标准(ISO/IEC 14882:2011):
- 18.6 Dynamic memory management [support.dynamic]
参阅
解分配函数 (函数) | |
(C++11) |
获得当前的 new 处理函数 (函数) |
注册一个 new 处理函数 (函数) | |
(C++17 中弃用)(C++20 中移除) |
获得未初始化存储 (函数模板) |
分配内存 (函数) | |
(C++17) |
分配对齐的内存 (函数) |