new 表达式

来自cppreference.com
< cpp‎ | language
 
 
 
表达式
概述
值类别(左值 lvalue 、右值 rvalue 、亡值 xvalue )
求值顺序(序列点)
常量表达式
不求值表达式
初等表达式
lambda 表达式(C++11)
字面量
整数字面量
浮点字面量
布尔字面量
字符字面量,包含转义序列
字符串字面量
空指针字面量(C++11)
用户定义字面量(C++11)
运算符
赋值运算符a=b, a+=b, a-=b, a*=b, a/=b, a%=b, a&=b, a|=b, a^=b, a<<=b, a>>=b
自增与自减++a, --a, a++, a--
算术运算符+a, -a, a+b, a-b, a*b, a/b, a%b, ~a, a&b, a|b, a^b, a<<b, a>>b
逻辑运算符a||b, a&&b, !a
比较运算符a==b, a!=b, a<b, a>b, a<=b, a>=b, a<=>b(C++20)
成员访问运算符a[b], *a, &a, a->b, a.b, a->*b, a.*b
其他运算符a(...), a,b, a?b:c
运算符的替代表示
优先级和结合性
折叠表达式(C++17)
new 表达式
delete 表达式
throw 表达式
alignof
sizeof
sizeof...(C++11)
typeid
noexcept(C++11)
运算符重载
类型转换
隐式转换
const_cast
static_cast
reinterpret_cast
dynamic_cast
显式转换 (T)a, T(a)
用户定义转换
 

创建并初始化拥有动态存储期的对象,即生存期不为它们的创建所在的作用域限制的对象。

目录

[编辑] 语法

::(可选) new (placement_params)(可选) ( type ) initializer(可选) (1)
::(可选) new (placement_params)(可选) type initializer(可选) (2)
1) 尝试创建类型 id type 所指代的类型的一个对象,它可以是数组类型,可以包含指定符 auto (C++11 起) 或占位符类型 decltype(auto) (C++17 起)
2) 同上,但 type 不能包含括号:
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 后,第一维是核心常量表达式,则程序为病态(发布编译时错误);
  • 否则,若要调用的分配函数是抛出的,则 new 表达式返回请求结果类型的空指针
(C++14 起)
  • 否则, new 表达式不调用分配函数,并取而代之地抛出 std::bad_array_new_length 类型或自之导出的异常
(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 被用于分配 charunsigned charstd::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 起)

若提供 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) 类的成员函数所封装。

在分配对齐超出 __STDCPP_DEFAULT_NEW_ALIGNMENT__ 的对象,或这种对象的数组时, new 表达式将对齐要求作为第二参数传递给分配函数(对于布置形式, placement_params 出现在对齐后,作为第三、第四等参数)。若重载决议失败(发生于类指定分配函数以不同签名定义时,因为它隐藏全局版本),重载决议尝试第二次,在参数列表中不带对齐。这允许无对齐类指定分配函数优先于全局具对齐分配函数。

(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 ,在所得内存区域构造单个对象。
  • initializer 是参数的花括号环绕列表,则对象被列表初始化
(C++11 起)
  • type 是数组类型,则初始化一个数组的对象。
  • 若不存在 initializer ,则每个元素都被默认初始化
  • initializer 是一对空括号,则每个元素都被值初始化
  • initializer 是参数的花括号环绕列表,则数组被聚合初始化
(C++11 起)

若初始化因抛异常终止(例如来自析构函数),则若 new 表达式分配任何存储,则它调用适当的解分配函数:对于非数组 typeoperator delete ,对于数组 typeoperator 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
} // memory leak if g() throws

为简化动态分配的对象管理, new 表达式的结果通常存储于智能指针std::auto_ptr (C++17 前) std::unique_ptrstd::shared_ptr (C++11 起)。这些指针保证在上述情形执行 delete 表达式。

[编辑] 关键词

new


[编辑] 缺陷报告

下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。

DR 应用于 出版时的行为 正确行为
CWG 1992 C++14 new (std::nothrow) int[N] 可能抛出 bad_array_new_length 改为返回空指针