- 创建并初始化拥有
动态存储期
的对象,这些对象的生存期不受它们创建时所在的作用域限制 - 动态存储器对象的存储是通过使用
动态内存分配
函数来按请求进行分配和解分配的
语法
::(可选) new (布置参数)(可选) (类型) 初始化器(可选) (1)
::(可选) new (布置参数)(可选) 类型 初始化器(可选) (2)
1、类型不能包括()
new int(*[10])(); // 错误:分析成 (new int) (*[10]) ()
new (int (*[10])()); // OK:分配 10 个函数指针的数组
2、无括号的 类型 是贪心的:它将包含可以是声明符一部分的所有记号:
new int + 1; // OK:分析成 (new int) + 1,增加 new int 所返回的指针
new int * 1; // 错误:分析成 (new int*) (1)
3、
double* p = new double[]{
1,2,3}; // 创建 double[3] 类型的数组
auto p = new auto('c'); // 创建单个 char 类型的对象。p 是一个 char*
auto q = new std::integral auto(1); // OK: q 是一个 int*
auto q = new std::floating_point auto(true) // 错误:不满足类型约束
auto r = new std::pair(1, true); // OK: r 是一个 std::pair<int, bool>*
auto r = new std::vector; // 错误:无法推导元素类型
4、new一个动态数组(这是唯一直接创建大小在运行时定义的数组的方法)
- 第一维可以是整数类型、枚举类型、拥有单个到整数或枚举类型的非 explicit 转换函数的类类型 (C++14 前)、任何能转换成 std::size_t 的表达式
- 第一维之外的所有维都必须指定为正的整数常量表达式(C++14 前)、类型为 std::size_t 的经转换的常量表达式 (C++14 起)
- 第一维为零是可接受的,分配函数也会被调用。
- 注意:std::vector 提供了与一维的动态数组类似的功能。
-
常量表达式:定义能在编译时求值的表达式
int n = 42;
double a[n][5]; // 错误
auto p1 = new double[n][5]; // OK
auto p2 = new double[5][n]; // 错误:仅第一维可为非常量
auto p3 = new (double[n][5]); // 错误:语法 (1) 不能用于动态数组
解释
- new表达式尝试申请空间,并在已申请的存储空间上,尝试构造并初始化一个无名的对象或者对象数组。
- new表达式返回一个指向其所构造的对象或者对象数组的纯右值指针
布局new
什么叫做布局new
若提供了 布置参数,则将它们作为额外实参传递给分配函数。这些分配函数被称作“布局 new”,这来源于标准分配函数 void* operator new(std::size_t, void*),它直接返回未更改的第二实参。它被用于在已分配的存储中构造对象:
// 在任何块作用域内……
{
alignas(T) unsigned char buf[sizeof(T)];
// 静态分配拥有自动存储期的存储,对任何对象类型 `T` 足够大。
T* tptr = new(buf) T; // 构造一个 `T` 对象,将它直接置于
// 你预分配的位于内存地址 `buf` 的存储。
tptr->~T(); // 如果程序依赖对象的析构函数的副作用,你必须**手动**调用它。
}
注意:分配器 (Allocator) 类的各成员函数封装了此功能。
- 若不抛出的分配函数(例如 new(std::nothrow) T 所选择的)因为分配失败返回空指针,则 new 表达式立即返回,而不会试图初始化对象或调用解分配函数。(也就是说不能对返回值释放空间)
- 若将空指针作为实参传给不分配的布置 new 表达式,这会使得被选择的标准不分配布置分配函数返回空指针,则行为未定义。
作用:
传统的new操作符只能在动态内存中得到空间,如果我们想要重一个静态内存中得到一个空间,该怎么做呢?
就用布局new操作符
#include <iostream>
#include <new>
using namespace std;
int main()
{
char buffer[10];
char *p = new (buffer) char[sizeof(buffer)];
p[9] = 'a';
cout<<buffer[9];
return 0;
}
p指针指向了buffer的内存空间,可以直接对buffer进行写值,而且p等于buffer数组的首地址。
注意:这里不能对p进行释放空间。
布局new操作符,能够让你指定要使用的位置。
分配
new 表达式通过调用 分配函数适当的分配存储
- 若 类型 是非数组类型,则函数名是 operator new。
- 若 类型 是数组类型,则函数名是 operator new[]。
正如分配函数中所诉,C++程序可提供这些函数的全局和类特有的替换函数
- 若 new 表达式以可选的 :: 运算符开始,如 ::new T 或 ::new T[n],则忽略类特有替换函数(在全局作用域中查找函数)
- 否则,若 T 是类类型,则从 T 的类作用域中开始查找。
在调用分配函数时,new表达式将请求的字节数作为std::size_t
类型的第一参数传递给它,该参数对于非数组T精确地为sizeof(T)
数组的分配中可能带有一个未指明的开销(overhead
),而且每次调用new的这个开销可能不同,除非选择的分配函数是标准非分配形式。new 表达式所返回的指针等于分配函数所返回的指针加上该值。
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)
许多实现使用数组开销的存储数组中的对象数量,它为delete[]
表达式所用,以进行正确数量的析构函数调用。
另外,若 new 被用于分配 char、unsigned char 或 std::byte (C++17 起)的数组,则它可能从分配函数请求额外内存,以此保证所有不大于请求数组大小的类型的对象,当将其放入所分配的数组中时能够正确对齐。
构造
new表达式所创建的对象按照下列规则初始化:
- 对于非数组的类型,在所得内存区域中构造单个对象
- 如果没有初始化器,则对象被
默认初始化
- 如果初始化器是带括号的实参列表,则对象被
直接初始化
- 如果初始化器是花括号包围的实参列表,则对象被
列表初始化
(C++11起)
- 如果没有初始化器,则对象被
- 如果类型是数组类型,则初始化一个数组的对象
- 如果没有初始化器,则对象被
默认初始化
- 如果初始化器是一对空括号,则每个元素被
值初始化
- 若 初始化器 是花括号包围的实参列表,则数组被
聚合初始化
。
- 如果没有初始化器,则对象被
如果初始化因抛出异常而终止(例如来自构造函数),则如果new表达式中已经分配了任何存储,则它将调用适当的解分配函数:
- 对于非数组类型,调用
operator delete
- 对于数组类型,调用
operator delete[]
若 new 表达式使用::new
语法,则在全局作用域查找解分配函数,否则若 T 是类类型,则在 T 的作用域查找。
- 若失败的是常规(非布局)分配函数,则遵循 delete 表达式中所述的规则查找解分配函数。
- 对于失败的布局new,与之匹配的解分配函数中除第一个外的所有形参的类型,必须与布置 new 的各形参类型相同。(???)对解分配函数的调用中,将先前从分配函数取得的值作为第一实参,将对齐作为可选的对齐参数, (C++17 起)并将 布置参数(若存在)作为额外的布置参数。若找不到解分配函数,则不解分配内存。
内存泄漏
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表达式的结果存储在智能指针中
,这些指针保证在上述情形中执行delete表达式