C/C++编程:new表达式

  • 创建并初始化拥有动态存储期的对象,这些对象的生存期不受它们创建时所在的作用域限制
  • 动态存储器对象的存储是通过使用动态内存分配函数来按请求进行分配和解分配的

语法

::(可选) 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表达式

猜你喜欢

转载自blog.csdn.net/zhizhengguan/article/details/114900805