new 表达式
创建并初始化拥有动态存储期的对象,即生存期不为它们的创建所在的作用域限制的对象。
目录 |
[编辑] 语法
::(可选) new (placement_params)(可选) ( type ) initializer(可选)
|
(1) | ||||||||
::(可选) new (placement_params)(可选) type initializer(可选)
|
(2) | ||||||||
new int(*[10])(); // 错误:分析成 (new int) (*[10]) () new (int (*[10])()); // okay :分配 10 个指向函数的指针的数组
另外,无括号的 type 是贪心的:它将包含任何能是声明器一部分的记号:
new int + 1; // okay :分析成 (new int) + 1 ,增加 new int 所返回的指针 new int * 1; // 错误:分析成 (new int*) (1)
注意:若 auto 用于 type 中,则 initializer 不是可选的:要求它推导出替代 auto 的类型:
auto p = new auto('c'); // 创建单个类型 char 的对象。 p 为 char*
[编辑] 解释
new 表达式试图分配存储,并试图于被分配存储构造并初始化一个无名对象,或一个无名数组。 new 表达式返回指向被构造对象的指针,或若构造的是数组,则为指向数组首元素的指针。
若 type 是数组类型,则所有异于第一维的维必须以正的 std::size_t 类型整数常量表达式 (C++14 前)被转换常量表达式 (C++14 起)指定,但第一维可以是任何能转换成 std::size_t 的表达式。这是仅有的直接创建大小在运行时定义的数组的方法,这种数组常被称作动态数组:
int n = 42; double a[n][5]; // 错误 auto p1 = new double[n][5]; // okay auto p2 = new double[5][n]; // 错误
下列情况中指定第一维的表达式是错误的:
- 表达式是非类类型且其值在转换到 std::size_t 前是负的;
- 表达式拥有类类型且其值在用户定义转换函数后,在第二次标准转换前是负的;
- 表达式的值大于某些实现定义极限;
- 值小于提供于花括号初始化器中的数组元素数量(包含字符串字面量中的终止
'\0')。
若第一维值因为任何以上原因错误,
- 若在转换到 std::size_t 后,第一维是核心常量表达式,则程序为病式(发布编译时错误);
|
(C++14 起) |
|
(C++11 起) |
第一维为零是可接受的,且分配函数会得到调用。
注意: std::vector 为一维动态数组提供类似的功能。
[编辑] 分配
new 表达式通过调用适当的分配函数分配存储。若 type 是非数组类型,则函数名是 operator new 。若 type 是数组类型,则函数名是 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 的数组,则它可能从分配函数请求额外内存,若需要保证所有不大于请求数组大小的类型的正确对齐,若该类型对象之后要被放入分配的数组。
|
允许 new 表达式消除或组合通过可替换分配函数进行的分配。在消除的情况下,存储可以由编译器提供,而无需调用分配函数(这亦允许优化掉不使用的 new 表达式)。在组合的情况下, new 表达式 E1 所做的分配可以扩展到提供另一个 new 表达式 E2 要用的存储,若以下全体为 true : 1) E1 所分配对象的生存期严格包含 E2 所分配对象的生存期,
2) E1 与 E2 将调用同一可替换全局分配函数
3) 对于抛出的分配函数, E1 与 E2 中的异常将首先为同一处理函数所捕捉。
注意此优化仅在使用 new 表达式时允许,而非任何调用可替换分配函数的方法: delete [] new int[10]; 能被优化掉,但 operator delete(operator new(10)); 不能。 |
(C++14 起) |
[编辑] 布置 new
若提供 placement_params ,则将它们作为额外参数传递给分配函数。这些分配函数被称作“布置 new ”,根据标准分配函数 void* operator new(std::size_t, void*) ,它们简单地返回不更改的第二参数。这被用于在已分配的存储构造对象:
char* ptr = new char[sizeof(T)]; // 分配内存 T* tptr = new(ptr) T; // 在已分配存储(“位置”)构造 tptr->~T(); // 析构 delete[] ptr; // 解分配内存
注意:分配器 (Allocator) 类的成员函数封装此功能。
|
在分配对齐超出 |
(C++17 起) |
new T; // 调用 operator new(sizeof(T)) // (C++17) 或 operator new(sizeof(T), std::align_val_t(alignof(T)))) new T[5]; // 调用 operator new[](sizeof(T)*5 + overhead) // (C++17) 或 operator new(sizeof(T)*5+overhead, std::align_val_t(alignof(T)))) new(2,f) T; // 调用 operator new(sizeof(T), 2, f) // (C++17) 或 operator new(sizeof(T), std::align_val_t(alignof(T)), 2, f)
若选择不抛出重载,例如以 new(std::nothrow) T; ,则分配函数可能返回空指针,若如此则 new 表达式立即返回,而不会试图初始化对象或调用解分配函数。若标准布置分配函数返回空指针,则行为未定义,这在用户传递空指针为参数的情况可能。 (C++17 起)
[编辑] 构造
new 表达式创建的对象按照下列规则初始化:
- 对于非数组
type,在所得内存区域构造单个对象。
|
(C++11 起) |
- 若 type 是数组类型,则初始化一个数组的对象。
|
(C++11 起) |
若初始化因抛异常终止(例如来自析构函数),则若 new 表达式分配任何存储,则它调用适当的解分配函数:对于非数组 type 的 operator delete ,对于数组 type 的 operator delete[] 。若 new 表达式使用 ::new 语法,则在全局作用域查找解分配函数,否则若 T 是类类型则在 T 的作用域查找。若失败的分配函数是通常的(非布置),则遵循描述于 delete 表达式。的规则查找解分配函数。对于失败的布置 new ,匹配解分配函数的所有参数类型,除了首参数,必须等同于布置 new 的参数类型。将先前从分配函数取得的值作为第一参数,将对齐作为可选的对齐参数, (C++17 起),及将 placement_params 作为额外的布置参数,若它存在,传递给解分配函数并调用。若找不到解分配函数,则不解分配内存。
[编辑] 内存泄漏
new 表达式所创建的对象(拥有动态存储期的对象)持续到将 new 表达式所返回的指针用于匹配的 delete 表达式。若指针的原值丢失,则对象变为不可达,且无法解分配:内存泄漏发生。
这在下列情况下可能发生。若对指针赋值:
int* p = new int(7); // 动态分配的 int 带值 7 p = nullptr; // 内存泄漏
或若指针离开作用域:
void f() { int* p = new int(7); } // 内存泄漏
或因为异常
void f() { int* p = new int(7); g(); // 可能抛出 delete p; // 若无异常则 ok } // 若 g() 抛出则内存泄漏
为简化动态分配的对象管理,通常将 new 表达式的结果存储于智能指针: std::auto_ptr 、 (C++17 前) std::unique_ptr 或 std::shared_ptr (C++11 起)。这些指针保证在上述情形执行 delete 表达式。
[编辑] 关键词
[编辑] 缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
| DR | 应用于 | 出版时的行为 | 正确行为 |
|---|---|---|---|
| CWG 1992 | C++14 | new (std::nothrow) int[N] 可能抛出 bad_array_new_length | 改为返回空指针 |