C++
编译器支持
自由(freestanding)与宿主(hosted)
语言
标准库
标准库头文件
具名要求
特性测试宏 (C++20)
语言支持库
概念库 (C++20)
诊断库
内存管理库
元编程库 (C++11)
通用工具库
容器库
迭代器库
范围库 (C++20)
算法库
字符串库
文本处理库
数值库
日期和时间库
输入/输出库
文件系统库 (C++17)
并发支持库 (C++11)
执行控制库 (C++26)
技术规范
符号索引
外部库
[编辑] C++ 语言
通用主题
预处理器
注释
关键词
转义序列
流程控制
条件执行语句
if
switch
迭代语句(循环)
for
range-for (C++11)
while
do-while
跳转语句
continue - break
goto - return
函数
函数声明
Lambda 函数表达式
inline 说明符
动态异常规范 (直到 C++17*)
noexcept 说明符 (C++11)
异常
throw 表达式
try 块
catch 处理程序
命名空间
命名空间声明
命名空间别名
类型
基本类型
枚举类型
函数类型
类/结构体类型
联合类型
说明符
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
存储期说明符
初始化
默认初始化
值初始化
零初始化
复制初始化
直接初始化
聚合初始化
列表初始化 (C++11)
常量初始化
引用初始化
表达式
值类别
求值顺序
运算符
运算符优先级
替代表示
字面量
布尔 - 整型 - 浮点型
字符 - 字符串 - nullptr (C++11)
用户定义 (C++11)
工具
属性 (C++11)
类型
typedef 声明
类型别名声明 (C++11)
类型转换
隐式转换
static_cast
const_cast
显式转换
dynamic_cast
reinterpret_cast
内存分配
new 表达式
delete 表达式
类
类声明
构造函数
this 指针
访问说明符
friend 说明符
类特有的函数属性
虚函数
override 说明符 (C++11)
final 说明符 (C++11)
explicit (C++11)
static
特殊成员函数
默认构造函数
复制构造函数
移动构造函数 (C++11)
复制赋值
移动赋值 (C++11)
析构函数
模板
类模板
函数模板
模板特化
参数包 (C++11)
杂项
内联汇编
C++ 历史
[编辑] 表达式
通用
值类别
求值顺序
常量表达式
主表达式
Lambda 表达式 (C++11)
Requires 表达式 (C++20)
包索引表达式 (C++26)
潜在求值表达式
字面量
整数字面量
浮点数字面量
布尔字面量
字符字面量
转义序列
字符串字面量
空指针字面量 (C++11)
用户定义字面量 (C++11)
运算符
赋值运算符
递增和递减
算术运算符
逻辑运算符
比较运算符
成员访问运算符
其他运算符
new 表达式
delete 表达式
throw 表达式
alignof
sizeof
sizeof... (C++11)
typeid
noexcept (C++11)
折叠表达式 (C++17)
运算符的替代表示
优先级和结合性
运算符重载
默认比较 (C++20)
转换
隐式转换
显式转换
常用算术转换
用户定义转换
const_cast
static_cast
dynamic_cast
reinterpret_cast
[编辑]
创建并初始化具有动态存储期的对象,即生命期不一定受其创建范围限制的对象。
目录
1 语法
2 解释
3 动态数组
4 分配
4.1 定位 new
5 初始化
5.1 初始化失败
6 内存泄漏
7 注释
8 关键词
9 缺陷报告
10 参阅
[编辑] 语法
::(可选) new (类型 ) new-initializer (可选)
(1)
::(可选) new 类型 new-initializer (可选)
(2)
::(可选) new (placement-args ) (类型 ) new-initializer (可选)
(3)
::(可选) new (placement-args ) 类型 new-initializer (可选)
(4)
1,2) 尝试创建由类型标识 类型 指示的类型的对象,它可以是数组类型,并且可以包含占位符类型说明符(C++11 起),或者包含其参数将通过类模板参数推导进行推导的类模板名称(C++17 起)。
3,4) 同 (1,2),但为分配函数提供附加参数,参见定位 new。
[编辑] 解释
类型
-
目标类型标识符
new-initializer
-
一个用括号括起来的表达式列表或用花括号括起来的初始化器列表(C++11 起)
placement-args
-
额外的定位参数
new 表达式尝试分配存储,然后尝试在分配的存储中构造并初始化一个无名对象或一个无名对象数组。new 表达式返回一个指向已构造对象的纯右值指针,如果构造的是对象数组,则返回指向数组初始元素的指针。
如果 类型 包含括号,则需要使用语法 (1) 或 (3)。
new int(*[10])(); // error: parsed as (new int) (*[10]) ()
new (int (*[10])()); // okay: allocates an array of 10 pointers to functions
此外,类型 会被贪婪地解析:它将包含所有可以作为声明符一部分的标记。
new int + 1; // okay: parsed as (new int) + 1, increments a pointer returned by new int
new int * 1; // error: parsed as (new int*) (1)
如果满足以下条件,则 new-initializer 不可省略:
类型 是未知边界的数组,
类型 中使用了占位符,即 auto 或 decltype(auto)(C++14 起),可能与类型约束结合使用(C++20 起),
(C++11 起)
类型 中使用了需要推导其参数的类模板。
(C++17 起)
double* p = new double[]{1, 2, 3}; // creates an array of type double[3]
auto p = new auto('c'); // creates a single object of type char. p is a char*
auto q = new std::integral auto(1); // OK: q is an int*
auto q = new std::floating_point auto(true) // ERROR: type constraint not satisfied
auto r = new std::pair(1, true); // OK: r is a std::pair
auto r = new std::vector; // ERROR: element type can't be deduced
[编辑] 动态数组
如果 类型 是数组类型,则除第一个维度之外的所有维度都必须指定为正的整型常量表达式(C++14 前)转换为 std::size_t 类型的常量表达式(C++14 起),但(仅在使用非括号语法 (2) 和 (4) 时)第一个维度可以是整型、枚举类型或具有单个非显式转换函数到整型或枚举类型的类类型表达式(C++14 前)任何可转换为 std::size_t 的表达式(C++14 起)。这是直接创建在运行时定义大小的数组的唯一方法,此类数组通常被称为动态数组。
int n = 42;
double a[n][5]; // error
auto p1 = new double[n][5]; // OK
auto p2 = new double[5][n]; // error: only the first dimension may be non-constant
auto p3 = new (double[n][5]); // error: syntax (1) cannot be used for dynamic arrays
如果第一个维度中的值(如果需要,转换为整型或枚举类型)为负,则行为未定义。
(C++11 前)
在以下情况下,指定第一个维度的表达式值无效:
表达式是非类类型,其在转换为 std::size_t 之前的值为负;表达式是类类型,其在用户定义的转换函数之后和第二次标准转换之前的值为负;表达式的值大于某个实现定义的最大值;值小于花括号初始化器列表中提供的数组元素数量(包括字符串字面量末尾的 '\0')。
如果第一个维度中的值由于上述任何原因无效,则:
如果在转换为 std::size_t 后,第一个维度是核心常量表达式并且它可能被求值,则程序格式错误,否则,如果将要调用的分配函数是非抛出的(包括未声明 noexcept 的 std::nothrow 重载),则 new 表达式返回所需结果类型的空指针,否则,new 表达式不调用分配函数,而是抛出类型为std::bad_array_new_length 的处理程序能够匹配的异常。
(C++11 起)
第一个维度为零是允许的,并且会调用分配函数。
如果 new-initializer 是花括号初始化器列表,并且第一个维度可能被求值且不是核心常量表达式,则会检查从空初始化器列表复制初始化数组假设元素的语义约束。
(C++11 起)
[编辑] 分配
new 表达式通过调用适当的分配函数来分配存储。如果 类型 是非数组类型,则函数名为 operator new。如果 类型 是数组类型,则函数名为 operator new[]。
如分配函数中所述,C++ 程序可以为这些函数提供全局和类特定的替换。如果 new 表达式以可选的 :: 运算符开头,如 ::new T 或 ::new T[n],则类特定的替换将被忽略(该函数在全局作用域中查找)。否则,如果 T 是类类型,则查找从 T 的类作用域开始。
调用分配函数时,new 表达式将请求的字节数作为第一个类型为 std::size_t 的参数传递,对于非数组 T,这恰好是 sizeof(T)。
数组分配可能提供未指定的开销,此开销在每次 new 调用之间可能有所不同,除非所选分配函数是标准非分配形式。new 表达式返回的指针将与分配函数返回的指针偏移该值。许多实现使用数组开销来存储数组中的对象数量,delete[] 表达式使用此数量来调用正确数量的析构函数。此外,如果 new 表达式用于分配 char、unsigned char 或 std::byte(C++17 起) 的数组,如果需要,它可能会从分配函数请求额外的内存,以确保所有类型不大于请求数组大小的对象都能正确对齐(如果稍后将一个对象放入已分配的数组中)。
new 表达式可以省略或合并通过可替换分配函数进行的分配。在省略的情况下,存储可能由编译器提供,而无需调用分配函数(这也允许优化掉未使用的 new 表达式)。在合并的情况下,new 表达式 E1 的分配可以扩展以提供额外的存储给另一个 new 表达式 E2,如果满足以下所有条件:
1) E1 分配的对象的生命期严格包含 E2 分配的对象的生命期。
2) E1 和 E2 将调用相同的可替换全局分配函数。
3) 对于抛出异常的分配函数,E1 和 E2 中的异常将在同一个处理程序中首次捕获。
请注意,此优化仅在使用 new 表达式时才允许,而不是通过任何其他方法调用可替换分配函数:delete[] new int[10]; 可以被优化掉,但 operator delete(operator new(10)); 则不能。
(C++14 起)
在求值常量表达式期间,对分配函数的调用总是被省略。只有原本会导致调用可替换全局分配函数的 new 表达式才能在常量表达式中求值。
(C++20 起)
[编辑] 定位 new
如果提供了 placement-args,它们将作为附加参数传递给分配函数。此类分配函数被称为“定位 new”,得名于标准分配函数 void* operator new(std::size_t, void*),它只是原封不动地返回其第二个参数。这用于在已分配的存储中构造对象。
// within any block scope...
{
// Statically allocate the storage with automatic storage duration
// which is large enough for any object of type “T”.
alignas(T) unsigned char buf[sizeof(T)];
T* tptr = new(buf) T; // Construct a “T” object, placing it directly into your
// pre-allocated storage at memory address “buf”.
tptr->~T(); // You must **manually** call the object's destructor
// if its side effects is depended by the program.
} // Leaving this block scope automatically deallocates “buf”.
注意:此功能由分配器(Allocator)类的成员函数封装。
当分配其对齐要求超过 __STDCPP_DEFAULT_NEW_ALIGNMENT__ 的对象或此类对象的数组时,new 表达式将对齐要求(封装在 std::align_val_t 中)作为分配函数的第二个参数传递(对于定位形式,placement-arg 出现在对齐参数之后,作为第三、第四等参数)。如果重载解析失败(当定义了具有不同签名的类特定分配函数时发生,因为它隐藏了全局函数),则会尝试第二次重载解析,此时参数列表中不包含对齐参数。这允许对齐不敏感的类特定分配函数优先于全局对齐敏感的分配函数。
(C++17 起)
new T; // calls operator new(sizeof(T))
// (C++17) or operator new(sizeof(T), std::align_val_t(alignof(T))))
new T[5]; // calls operator new[](sizeof(T)*5 + overhead)
// (C++17) or operator new(sizeof(T)*5+overhead, std::align_val_t(alignof(T))))
new(2,f) T; // calls operator new(sizeof(T), 2, f)
// (C++17) or operator new(sizeof(T), std::align_val_t(alignof(T)), 2, f)
如果一个不抛出异常的分配函数(例如通过 new(std::nothrow) T 选择的那个)由于分配失败而返回空指针,则 new 表达式立即返回,它不会尝试初始化对象或调用 deallocation 函数。如果将空指针作为参数传递给不分配内存的定位 new 表达式,这会使所选的标准不分配内存的定位分配函数返回空指针,则行为是未定义的。
[编辑] 初始化
由 new 表达式创建的对象根据以下规则初始化。
如果 类型 不是数组类型,则单个对象在获得的内存区域中构造:
如果 new-initializer 不存在,则对象是默认初始化的。如果 new-initializer 是用括号括起来的表达式列表,则对象是直接初始化的。
如果 new-initializer 是用花括号括起来的初始化器列表,则对象是列表初始化的。
(C++11 起)
如果 类型 是数组类型,则初始化对象数组:
如果 new-initializer 不存在,则每个元素都默认初始化。
即使第一个维度为零,仍然需要满足默认初始化假设元素的语义约束。
如果 new-initializer 是一对括号,则每个元素都值初始化。
即使第一个维度为零,仍然需要满足值初始化假设元素的语义约束。
如果 new-initializer 是用花括号括起来的初始化器列表,则数组是聚合初始化的。
(C++11 起)
如果 new-initializer 是用括号括起来的非空表达式列表,则数组是聚合初始化的。
(C++20 起)
[编辑] 初始化失败
如果初始化通过抛出异常(例如来自构造函数)而终止,则程序会查找匹配的 deallocation 函数,然后:
如果可以找到合适的 deallocation 函数,则调用该 deallocation 函数来释放正在构造对象的内存。之后,异常在 new 表达式的上下文中继续传播。如果找不到明确匹配的 deallocation 函数,则传播异常不会导致对象内存被释放。这仅在被调用的分配函数不分配内存时才适用,否则很可能会导致内存泄漏。
匹配 deallocation 函数的查找范围确定如下:
如果 new 表达式不以 :: 开头,且分配类型是类类型 T 或类类型 T 的数组,则在 T 的类作用域中搜索 deallocation 函数的名称。否则,或者如果在 T 的类作用域中未找到任何内容,则通过在全局作用域中搜索来查找 deallocation 函数的名称。
对于非定位分配函数,使用正常的 deallocation 函数查找来查找匹配的 deallocation 函数(参见delete-expression)。
对于定位分配函数,匹配的 deallocation 函数必须具有相同数量的参数,并且除了第一个参数之外的每个参数类型都与分配函数的相应参数类型相同(在参数转换之后)。
如果查找找到一个匹配的 deallocation 函数,则将调用该函数;否则,将不调用任何 deallocation 函数。如果查找找到一个非定位 deallocation 函数,并且该函数作为定位 deallocation 函数,本应被选为分配函数的匹配,则程序格式错误。
在任何情况下,匹配的 deallocation 函数(如果有)必须是未删除且(C++11 起)在 new 表达式出现的位置可访问的。
struct S
{
// Placement allocation function:
static void* operator new(std::size_t, std::size_t);
// Non-placement deallocation function:
static void operator delete(void*, std::size_t);
};
S* p = new (0) S; // error: non-placement deallocation function matches
// placement allocation function
如果在 new 表达式中调用 deallocation 函数(由于初始化失败),则传递给该函数的参数确定如下:
第一个参数是从分配函数调用返回的值(类型为 void*)。其他参数(仅适用于定位 deallocation 函数)是传递给定位分配函数的 placement-args。
如果允许实现引入临时对象或复制任何参数作为调用分配函数的一部分,则未指定是否在分配函数和 deallocation 函数的调用中使用相同的对象。
[编辑] 内存泄漏
由 new 表达式创建的对象(具有动态存储期的对象)会一直存在,直到 new 表达式返回的指针在匹配的delete-expression中使用。如果指针的原始值丢失,对象将无法访问且无法释放:发生内存泄漏。
这可能在以下情况下发生:指针被赋值,
int* p = new int(7); // dynamically allocated int with value 7
p = nullptr; // memory leak
或者指针超出作用域,
void f()
{
int* p = new int(7);
} // memory leak
或者由于异常。
void f()
{
int* p = new int(7);
g(); // may throw
delete p; // okay if no exception
} // memory leak if g() throws
为了简化动态分配对象的管理,new 表达式的结果通常存储在智能指针中:std::auto_ptr (C++17 前)std::unique_ptr 或 std::shared_ptr(C++11 起)。这些指针保证在上述情况下执行 delete 表达式。
[编辑] 注意
Itanium C++ ABI 要求如果创建的数组的元素类型是 trivially destructible,则数组分配开销为零。MSVC 也是如此。
一些实现(例如 VS 2019 v16.7 之前的 MSVC)要求在非分配定位数组 new 中,如果元素类型不是 trivially destructible,则数组分配开销非零,这自 CWG 问题 2382 起不再符合标准。
一个非分配定位数组 new 表达式,它创建一个 unsigned char 或 std::byte(C++17 起) 的数组,可用于在给定存储区域上隐式创建对象:它结束与数组重叠的对象的生命期,然后隐式创建数组中具有隐式生命期类型的对象。
std::vector 为一维动态数组提供了类似的功能。
[编辑] 关键词
new
[编辑] 缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
缺陷报告
应用于
发布时的行为
正确的行为
CWG 74
C++98
第一个维度中的值必须为整型
允许枚举类型
CWG 299
C++98
第一个维度中的值必须具有整型或枚举类型
允许具有单个到整型或枚举类型的转换函数的类类型
CWG 624
C++98
当分配对象的大小超过实现定义限制时,行为未指定
在这种情况下不获取存储,并抛出异常
CWG 1748
C++98
非分配定位 new 需要检查参数是否为 null
null 参数导致未定义行为
CWG 1992
C++11
new (std::nothrow) int[N]可能抛出 std::bad_array_new_length
改为返回空指针
CWG 2102
C++98
不清楚初始化空数组时是否需要默认/值初始化为良构
需要
CWG 2382
C++98
非分配定位数组 new可能需要分配开销
此类分配开销被禁止
CWG 2392
C++11
即使第一个维度未被潜在求值,程序也可能格式错误
在这种情况下是良构的
P1009R2
C++11
数组边界无法在new 表达式中推导
允许推导
[编辑] 参阅
构造函数
复制省略
默认构造函数
delete
析构函数
初始化
聚合初始化
默认初始化
直接初始化
列表初始化
值初始化