内存分配
在本节课中,我们将学习内存管理的一个重要分支,即内存分配。
简介
C++ 中的显式内存管理具有较高的复杂性,但也为我们提供了强大的功能。遗憾的是,这一 C++ 特有的领域并不为很多人所熟知。
例如,我们可以直接在静态内存、预留区域甚至内存池中创建对象。这种功能在嵌入式领域的安全关键应用中尤为重要。
-
C++ 支持动态分配和释放内存。
-
动态内存(即堆)需要程序员显式地请求和释放。
-
我们可以使用
new和new[]操作符来分配内存,使用delete和delete[]操作符来释放内存。 -
编译器会自动管理栈上的内存。
智能指针可自动管理内存。
内存分配
new
借助 new 操作符,我们可以为某个类型的实例动态分配内存。
1int* i = new int; 2double* d = new double(10.0); 3Point* p = new Point(1.0, 2.0);
-
new会触发内存分配并完成对象初始化。 -
括号中的参数会直接传递给构造函数。
-
new返回指向对应对象的指针。 -
如果动态创建的对象属于类型继承体系,则会调用更多构造函数。
new[]
new[] 用于为 C 风格数组分配内存。新创建的对象必须具备默认构造函数。
1double* d = new double[5]; 2Point* p = new Point[10];
-
被分配对象的类必须拥有默认构造函数。
-
每个数组元素都会调用一次默认构造函数。
STL 容器和 C++ 字符串会自动管理其内存。
位置 new
位置 new 常用于在指定内存区域中实例化对象或 C 数组。此外,我们还可以对位置 new 进行全局或针对自定义数据类型的重载。这是 C++ 提供的一大优势。
1char* memory = new char[sizeof(Account)]; // 分配 std::size_t 大小的内存 2Account* acc = new(memory) Account; // 在 memory 中实例化 acc
-
必须包含头文件
<new>。 -
可以在类级别或全局范围内进行重载。
典型应用场景
-
显式内存分配
-
避免抛出异常
-
调试
内存分配失败
如果内存分配操作失败,new 和 new[] 会抛出 std::bad_alloc 异常。但有时我们不希望出现异常。因此,可以使用常量 std::nothrow 调用位置 new。此时若分配失败,将返回 nullptr。
1char* c = new(std::nothrow) char[10]; 2if (c) { 3 delete[] c; 4} else { 5 // 发生错误 6}
新内存处理函数
当内存分配失败时,可以使用 std::set_new_handler 注册自定义处理函数。std::set_new_handler 返回旧的处理函数,并接受一个可调用对象作为参数。可调用对象通常是一个函数、函数对象或 lambda 表达式。该处理函数应无参数且无返回值。可以通过调用 std::get_new_handler 获取当前正在使用的处理函数。
自定义处理函数允许我们实现特定的失败处理策略:
- 请求更多内存
- 使用
std::terminate终止程序 - 抛出
std::bad_alloc类型的异常
在下一课中,我们将学习如何释放内存。