
在C++编程中,动态内存管理是必须掌握的核心技能之一。new和delete作为C++提供的动态内存管理操作符,承担着在堆内存中创建和销毁对象的重要职责。与C语言的malloc/free不同,new和delete不仅能完成内存分配,还会自动调用对象的构造函数和析构函数,使得它们成为C++面向对象编程中不可或缺的工具。
new 和 delete 的基本概念在了解 new 和 delete 之前,我们需要先明确静态内存和动态内存的概念。
#include <iostream>
// 全局变量,存储在静态内存中
int globalVar = 10;
void func() {
// 局部静态变量,存储在静态内存中
static int staticVar = 20;
std::cout << "Static variable: " << staticVar << std::endl;
}
int main() {
// 局部变量,存储在栈上
int localVar = 30;
std::cout << "Local variable: " << localVar << std::endl;
func();
return 0;
}
new 和 delete 表达式就是用于管理动态内存的工具。①内存分配
new 操作符时,它首先会调用 C++ 的内存分配函数(通常是 operator new,但用户也可以重载这个函数以提供自定义的内存分配策略)。operator new 会向堆(heap)请求足够的内存来存储指定类型的一个或多个对象。如果内存分配成功,它会返回一个指向该内存的指针;如果失败,则抛出 std::bad_alloc 异常(除非指定了nothrow版本)。②对象构造
new 操作符接着会调用对象的构造函数来初始化分配的内存。对于单个对象,它直接调用该对象的构造函数;对于对象数组,它调用默认构造函数(无参构造函数)来初始化数组中的每个元素。new 操作符会捕获这个异常,并使用 operator delete 释放之前分配的内存,然后重新抛出异常。③ 返回指针:一旦对象被成功构造,new 操作符就会返回指向该对象的指针。
new 操作符用于在堆(heap)上分配内存,并可能调用对象的构造函数来初始化内存。new 有两种形式:为单个对象分配内存和为对象数组分配内存。
①为单个对象分配内存
ClassName* pointer = new ClassName(args);ClassName 是要创建的对象类型,args 是传递给对象构造函数的参数(如果构造函数不需要参数,则省略)。new 操作符首先为 ClassName 类型的对象分配足够的内存,然后调用该类型的构造函数(如果有的话),并返回指向新分配内存的指针。
②为对象数组分配内存
ClassName* array = new ClassName[size];size 指定了要分配的数组的元素数量。new 操作符为 size 个 ClassName 类型的对象分配内存,但只调用默认构造函数(无参构造函数)来初始化这些对象。如果没有默认构造函数,则编译失败。
①对象析构:当调用 delete 或 delete[] 操作符时,它首先会调用指向对象的指针所指向的对象的析构函数(如果有的话)。对于 delete,它只调用单个对象的析构函数;对于 delete[],它会遍历数组中的每个对象并调用它们的析构函数。
②内存释放:
delete 或 delete[] 操作符会调用 C++ 的内存释放函数(通常是 operator delete 或 operator delete[],但同样可以重载)。operator delete 或 operator delete[] 会将之前分配的内存归还给堆,以便后续的内存分配请求可以使用。③指针置空(可选):虽然 delete 或 delete[] 操作符本身不会将指针置为 nullptr,但这是一个良好的编程实践,以避免野指针的出现。应该在 delete 或 delete[] 调用后立即将指针设置为 nullptr。
1.3.2 delete 操作符的用法delete 操作符用于释放之前通过 new 分配的内存。与 new 类似,delete 也有两种形式:用于单个对象和对象数组。
①释放单个对象的内存
delete pointer;pointer 是指向之前通过 new 分配的内存的指针。delete 操作符首先调用对象的析构函数(如果有的话),然后释放与该对象关联的内存。
②释放对象数组的内存
delete[] array;对于通过 new[] 分配的对象数组,必须使用 delete[] 来释放内存。delete[] 操作符会遍历数组中的每个对象,并调用它们的析构函数(如果有的话),然后释放整个数组的内存。
new 和 delete 的基本使用下面是一个简单的示例,演示如何使用 new 和 delete 分配和释放单个对象:
#include <iostream>
int main() {
// 使用 new 分配一个整数对象
int* ptr = new int;
*ptr = 10;
std::cout << "Value: " << *ptr << std::endl;
// 使用 delete 释放内存
delete ptr;
return 0;
}
使用 new 分配了一个整数对象,并将其初始化为 10。然后,使用 delete 释放了该对象所占用的内存。
下面是一个分配和释放数组的示例:
#include <iostream>
int main() {
// 使用 new 分配一个包含 5 个整数的数组
int* arr = new int[5];
for (int i = 0; i < 5; i++) {
arr[i] = i;
}
// 输出数组元素
for (int i = 0; i < 5; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
// 使用 delete[] 释放数组内存
delete[] arr;
return 0;
}
使用 new 分配了一个包含 5 个整数的数组,并对其进行初始化。然后,使用 delete[] 释放了该数组所占用的内存。
new 表达式的初始化new 表达式可以在分配内存的同时对对象进行初始化。
#include <iostream>
int main() {
// 直接初始化
int* ptr1 = new int(10);
std::cout << "Value of ptr1: " << *ptr1 << std::endl;
// 值初始化
int* ptr2 = new int();
std::cout << "Value of ptr2: " << *ptr2 << std::endl;
delete ptr1;
delete ptr2;
return 0;
}
ptr1 使用直接初始化的方式将分配的整数对象初始化为 10,ptr2 使用值初始化的方式将分配的整数对象初始化为 0。
#include <iostream>
int main() {
// 初始化数组
int* arr1 = new int[5]{1, 2, 3, 4, 5};
for (int i = 0; i < 5; i++) {
std::cout << arr1[i] << " ";
}
std::cout << std::endl;
// 值初始化数组
int* arr2 = new int[5]();
for (int i = 0; i < 5; i++) {
std::cout << arr2[i] << " ";
}
std::cout << std::endl;
delete[] arr1;
delete[] arr2;
return 0;
}
arr1 使用初始化列表对数组元素进行初始化,arr2 使用值初始化的方式将数组元素初始化为 0。
new 和 delete 与类和对象new 和 delete 也可以用于动态创建和销毁类的对象。
#include <iostream>
class MyClass {
public:
MyClass() {
std::cout << "Constructor called" << std::endl;
}
~MyClass() {
std::cout << "Destructor called" << std::endl;
}
void print() {
std::cout << "Hello, World!" << std::endl;
}
};
int main() {
// 使用 new 创建对象
MyClass* obj = new MyClass;
obj->print();
// 使用 delete 销毁对象
delete obj;
return 0;
}
使用 new 创建 MyClass 对象时,会调用类的构造函数;使用 delete 销毁对象时,会调用类的析构函数。
#include <iostream>
class MyClass {
public:
MyClass() {
std::cout << "Constructor called" << std::endl;
}
~MyClass() {
std::cout << "Destructor called" << std::endl;
}
};
int main() {
// 使用 new 创建对象数组
MyClass* arr = new MyClass[3];
// 使用 delete[] 销毁对象数组
delete[] arr;
return 0;
}
使用 new 创建对象数组时,会为每个对象调用构造函数;使用 delete[] 销毁对象数组时,会为每个对象调用析构函数。
new 和 delete 的异常处理当 new 表达式无法分配所需的内存时,会抛出 std::bad_alloc 异常。为了避免程序崩溃,可以使用异常处理机制来捕获并处理该异常。
#include <iostream>
#include <new>
int main() {
try {
// 尝试分配大量内存
int* ptr = new int[1000000000];
delete[] ptr;
} catch (const std::bad_alloc& e) {
std::cerr << "Memory allocation failed: " << e.what() << std::endl;
}
return 0;
}
使用 try-catch 块捕获 std::bad_alloc 异常,并输出错误信息。
new 和 delete 的替代方案:智能指针虽然 new 和 delete 提供了动态内存管理的基本功能,但手动管理内存容易出现内存泄漏和悬空指针等问题。C++ 标准库提供了智能指针来自动管理动态内存,避免这些问题。
std::unique_ptrstd::unique_ptr 是一种独占式智能指针,它确保同一时间只有一个指针可以指向该对象。当 std::unique_ptr 离开作用域时,会自动释放所指向的对象。
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() {
std::cout << "Constructor called" << std::endl;
}
~MyClass() {
std::cout << "Destructor called" << std::endl;
}
void print() {
std::cout << "Hello, World!" << std::endl;
}
};
int main() {
// 创建 std::unique_ptr
std::unique_ptr<MyClass> ptr = std::make_unique<MyClass>();
ptr->print();
// 当 ptr 离开作用域时,对象会自动销毁
return 0;
}std::shared_ptrstd::shared_ptr 是一种共享式智能指针,多个 std::shared_ptr 可以指向同一个对象,并且会维护一个引用计数。当引用计数为 0 时,对象会自动销毁。
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() {
std::cout << "Constructor called" << std::endl;
}
~MyClass() {
std::cout << "Destructor called" << std::endl;
}
void print() {
std::cout << "Hello, World!" << std::endl;
}
};
int main() {
// 创建 std::shared_ptr
std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
std::shared_ptr<MyClass> ptr2 = ptr1;
ptr1->print();
ptr2->print();
// 当 ptr1 和 ptr2 都离开作用域时,对象会自动销毁
return 0;
}
内存分配底层实现:
void* operator new(size_t size) {
if (void* mem = malloc(size))
return mem;
else
throw std::bad_alloc();
}
void operator delete(void* mem) noexcept {
free(mem);
}重载类专属operator new:
class MyClass {
public:
static void* operator new(size_t size) {
std::cout << "Custom new for MyClass\n";
return ::operator new(size);
}
static void operator delete(void* p) {
std::cout << "Custom delete for MyClass\n";
::operator delete(p);
}
};①关键区别
特性 | new/delete | malloc/free |
|---|---|---|
类型安全 | 是 | 否 |
构造/析构调用 | 自动 | 手动 |
内存计算 | 自动 | 手动 |
异常处理 | 抛出异常 | 返回NULL |
重载方式 | 类/全局作用域 | 全局函数 |
数组处理 | 专用语法 | 需要手动计算 |
②混用危险示例
// 危险操作!
MyClass* p = (MyClass*)malloc(sizeof(MyClass));
p->MyClass(); // 手动调用构造函数(非常规操作)
// ...使用对象...
p->~MyClass(); // 手动调用析构函数
free(p);如果使用 new 分配了内存,但没有使用 delete 释放,就会导致内存泄漏。例如:
void leaky_function() {
int* p = new int[100];
// 忘记delete[] p;
}为了避免内存泄漏,应该始终确保在不再需要内存时使用 delete 释放它,或者使用智能指针来自动管理内存。
解决方法:使用智能指针
#include <memory>
void safe_function() {
auto p = std::make_unique<int[]>(100);
// 自动释放内存
}当使用 delete 释放内存后,指向该内存的指针就会变成悬空指针。如果继续使用悬空指针,会导致未定义行为。例如:
#include <iostream>
int main() {
int* ptr = new int;
*ptr = 10;
delete ptr;
// 使用悬空指针
std::cout << *ptr << std::endl;
return 0;
}为了避免悬空指针,在释放内存后,应该将指针置为 nullptr。
如果对同一块内存多次使用 delete 释放,会导致未定义行为。例如:
#include <iostream>
int main() {
int* ptr = new int;
delete ptr;
// 多次释放内存
delete ptr;
return 0;
}为了避免多次释放内存,应该确保每个 new 只对应一个 delete,并且在释放内存后将指针置为 nullptr。
#include <memory>
// 独占所有权
std::unique_ptr<MyClass> uptr(new MyClass());
// 共享所有权
std::shared_ptr<MyClass> sptr = std::make_shared<MyClass>();
// 数组支持(C++17)
auto arrPtr = std::make_unique<int[]>(10);class ResourceHolder {
int* resource;
public:
ResourceHolder(size_t size) : resource(new int[size]) {}
~ResourceHolder() { delete[] resource; }
// 禁用拷贝(C++11)
ResourceHolder(const ResourceHolder&) = delete;
ResourceHolder& operator=(const ResourceHolder&) = delete;
// 移动语义支持(C++11)
ResourceHolder(ResourceHolder&& other) : resource(other.resource) {
other.resource = nullptr;
}
};正确使用new和delete是C++开发者的基本功,需要注意:
现代C++(C++11及后续标准)提供了更安全的内存管理工具,建议在实际开发中优先使用智能指针和标准容器,尽量减少直接使用new/delete的需要。只有深入理解底层机制,才能更好地使用高层抽象工具。
using声明在模板编程中有着重要应用,如定义模板类型别名等。