命名空间
变体
操作

new 表达式

来自 cppreference.cn
< cpp‎ | language
 
 
C++ 语言
表达式
替用表示
字面量
布尔字面量 - 整数字面量 - 浮点字面量
字符字面量 - 字符串字面量 - nullptr (C++11)
用户定义字面量 (C++11)
工具
属性 (C++11)
类型
typedef 声明
类型别名声明 (C++11)
转型
内存分配
new 表达式
类特有函数属性
explicit (C++11)
static

特殊成员函数
模板
杂项
 
 

创建并初始化具有动态存储期的对象,即,对象的生命周期不一定受限于创建它们的作用域。

内容

[编辑] 语法

::(可选) new (type) new-initializer(可选) (1)
::(可选) new type new-initializer(可选) (2)
::(可选) new (placement-args) (type) new-initializer(可选) (3)
::(可选) new (placement-args) type new-initializer(可选) (4)
1,2) 尝试创建由 类型标识符 type 表示的 type 类型的对象,它可以是数组类型,并且可以包含占位符类型说明符(自 C++11 起),或者包含其参数将通过类模板实参推导来推导的类模板名称(自 C++17 起)
3,4)(1,2) 相同,但为分配函数提供额外的参数,参见 placement new

[编辑] 解释

type - 目标类型标识符
new-initializer - 圆括号包围的表达式列表花括号包围的初始化器列表(自 C++11 起)
placement-args - 额外的 placement 实参


new 表达式尝试分配存储,然后尝试在已分配的存储中构造和初始化单个未命名对象或未命名对象数组。new 表达式返回指向已构造对象的纯右值指针,或者,如果构造了对象数组,则返回指向数组初始元素的指针。

如果 type 包含圆括号,则需要语法 (1)(3)

new int(*[10])();    // error: parsed as (new int) (*[10]) ()
new (int (*[10])()); // okay: allocates an array of 10 pointers to functions

此外,type 被贪婪地解析:它将包含每个可以作为声明符一部分的标记

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 不是可选的:

  • 占位符type 中使用,即 auto decltype(auto)(自 C++14 起),可能与类型约束组合使用(自 C++20 起)
(自 C++11 起)
  • type 中使用了类模板,其参数需要被推导
(自 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<int, bool>*
auto r = new std::vector;        // ERROR: element type can't be deduced

[编辑] 动态数组

如果 type 是数组类型,则除第一维之外的所有维度都必须指定为正整数常量表达式(直到 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 之后,第一维是核心常量表达式并且它是潜在求值的,则程序是非良构的,
  • 否则,如果将被调用的分配函数是非抛出的(包括未声明为 noexceptstd::nothrow 重载),则 new 表达式返回所需结果类型的空指针,
  • 否则,new 表达式不调用分配函数,而是抛出类型为 std::bad_array_new_length 的处理程序可以匹配的类型的异常。
(自 C++11 起)

零的第一维是可以接受的,并且会调用分配函数。

如果 new-initializer 是花括号包围的初始化器列表,并且第一维是潜在求值的且不是核心常量表达式,则检查从空初始化器列表复制初始化数组的假设元素的语义约束。

(自 C++11 起)

[编辑] 分配

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(自 C++17 起) 数组,则如果需要保证稍后放置在已分配数组中的所有类型对象(不大于请求的数组大小)的正确对齐,则它可能会从分配函数请求额外的内存。

new 表达式允许省略或组合通过可替换的分配函数进行的分配。在省略的情况下,存储可以由编译器提供,而无需调用分配函数(这也允许优化掉未使用的 new 表达式)。在组合的情况下,如果满足以下所有条件,则 new 表达式 E1 所做的分配可以扩展为为另一个 new 表达式 E2 提供额外的存储

1) E1 分配的对象的生命周期严格包含 E2 分配的对象的生命周期。
2) E1E2 将调用相同的可替换全局分配函数。
3) 对于抛出异常的分配函数,E1E2 中的异常将首先在同一处理程序中捕获。

请注意,此优化仅在使用 new 表达式时才允许,而不是任何其他调用可替换分配函数的方法:delete[] new int[10]; 可以被优化掉,但 operator delete(operator new(10)); 不能。

(自 C++14 起)

常量表达式的求值期间,始终省略对分配函数的调用。只有那些原本会导致调用可替换全局分配函数的 new 表达式才能在常量表达式中求值。

(自 C++20 起)

[编辑] Placement new

如果提供了 placement-args,则它们将作为额外的参数传递给分配函数。此类分配函数被称为 “placement 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 形式,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 表达式立即返回,它不会尝试初始化对象或调用释放分配函数。如果将空指针作为参数传递给非分配 placement new 表达式,这使得选定的标准非分配 placement 分配函数返回空指针,则行为未定义。

[编辑] 初始化

new 表达式创建的对象根据以下规则初始化。

如果 type 不是数组类型,则在获得的内存区域中构造单个对象

  • 如果 new-initializer 缺失,则对象被默认初始化
  • 如果 new-initializer 是圆括号包围的表达式列表,则对象被直接初始化
  • 如果 new-initializer 是花括号包围的初始化器列表,则对象被列表初始化
(自 C++11 起)

如果 type 是数组类型,则初始化对象数组

  • 即使第一维为零,仍然需要满足默认初始化假设元素的语义约束。
  • 如果 new-initializer 是一对圆括号,则每个元素都被值初始化
  • 即使第一维为零,仍然需要满足值初始化假设元素的语义约束。
  • 如果 new-initializer 是花括号包围的初始化器列表,则数组被聚合初始化
(自 C++11 起)
  • 如果 new-initializer 是圆括号包围的非空表达式列表,则数组被聚合初始化
(自 C++20 起)

[编辑] 初始化失败

如果初始化因抛出异常而终止(例如,从构造函数中),则程序查找匹配的释放分配函数,然后

  • 如果可以找到合适的释放分配函数,则调用释放分配函数以释放正在构造对象的内存。之后,异常继续在 new 表达式的上下文中传播。
  • 如果找不到明确匹配的释放分配函数,则传播异常不会导致对象的内存被释放。仅当调用的分配函数不分配内存时,这才适用,否则很可能导致内存泄漏。

匹配的释放分配函数的查找作用域确定如下

  • 如果 new 表达式不以 :: 开头,并且分配的类型是类类型 T 或类类型 T 的数组,则在 T 的类作用域中搜索释放分配函数的名称。
  • 否则,如果在 T 的类作用域中未找到任何内容,则通过在全局作用域中搜索来查找释放分配函数的名称。

对于非 placement 分配函数,使用正常的释放分配函数查找来查找匹配的释放分配函数(参见 delete 表达式)。

对于 placement 分配函数,匹配的释放分配函数必须具有相同数量的参数,并且除第一个参数之外的每个参数类型都与分配函数的相应参数类型相同(在参数转换之后)。

  • 如果查找找到单个匹配的释放分配函数,则将调用该函数;否则,将不调用释放分配函数。
  • 如果查找找到非 placement 释放分配函数,并且该函数(被视为 placement 释放分配函数)将被选为分配函数的匹配项,则程序是非良构的。

在任何情况下,匹配的释放分配函数(如果有)都必须是非删除的,并且(自 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 表达式中调用了释放分配函数(由于初始化失败),则传递给该函数的参数确定如下

  • 第一个参数是从分配函数调用返回的值(类型为 void*)。
  • 其他参数(仅适用于 placement 释放分配函数)是传递给 placement 分配函数的 placement-args

如果允许实现引入临时对象或复制任何参数作为调用分配函数的一部分,则不指定在调用分配函数和释放分配函数时是否使用同一对象。

[编辑] 内存泄漏

new 表达式创建的对象(具有动态存储期的对象)将持续存在,直到 new 表达式返回的指针在匹配的 delete 表达式中使用。如果指针的原始值丢失,则对象将变得不可访问且无法释放分配:发生内存泄漏

如果指针被赋值给,则可能发生这种情况

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_ptrstd::shared_ptr(自 C++11 起)。这些指针保证在上述情况下执行 delete 表达式。

[编辑] 注解

Itanium C++ ABI 要求,如果创建的数组的元素类型是平凡可析构的,则数组分配开销为零。MSVC 也是如此。

某些实现(例如,VS 2019 v16.7 之前的 MSVC)在非分配 placement 数组 new 上需要非零数组分配开销,如果元素类型不是平凡可析构的,则不再符合 CWG issue 2382 的规定。

创建 unsigned charstd::byte(自 C++17 起) 数组的非分配 placement 数组 new 表达式可用于在给定的存储区域上隐式创建对象:它结束与数组重叠的对象的生命周期,然后在数组中隐式创建隐式生命周期类型的对象。

std::vector 为一维动态数组提供类似的功能。

[编辑] 关键字

new

[编辑] 缺陷报告

以下行为变更缺陷报告已追溯应用于先前发布的 C++ 标准。

DR 应用于 已发布行为 正确行为
CWG 74 C++98 第一维中的值必须具有整数类型 允许枚举类型
CWG 299 C++98 第一维的值必须
具有整型或枚举类型
具有单个转换函数的类类型
到整型
或枚举类型是允许的
CWG 624 C++98 当分配对象的大小超过
实现定义的限制时,行为是未指定的
实现定义的限制
不获取存储,并且
在这种情况下抛出异常
CWG 1748 C++98 非分配式 placement new 需要
检查参数是否为空
空参数的未定义行为
CWG 1992 C++11 new (std::nothrow) int[N]
可能抛出 std::bad_array_new_length 异常
已更改为返回空指针
CWG 2102 C++98 默认/值初始化是否
在初始化空数组时需要良好定义,这一点尚不清楚
需要
CWG 2382 C++98 非分配式 placement 数组 new
可能需要分配开销
不允许此类分配开销
CWG 2392 C++11 即使
第一维不是潜在求值的,程序也可能是不良形式的
在这种情况下是良好形式的
P1009R2 C++11 数组边界可能无法
new 表达式中推导
允许推导

[编辑] 参见