内存管理简介
详细技术视频请看我的主页
C++ 提供了多种内存管理方式,包括传统的 C 风格的 malloc
和 free
,以及 C++ 专用的 new
和 delete
。
理解这些内存管理方法对于编写高效、安全的 C++ 程序至关重要。
本文将详细介绍这些内存管理方法,包含基本用法、复杂操作(如 realloc
),并配以实际案例和代码示例。
内存管理基础
在 C++ 程序中,内存主要分为以下几个区域:
- 栈(Stack):自动管理内存,存储局部变量和函数调用信息。内存分配和释放速度快,但空间有限。
- 堆(Heap):手动管理内存,用于动态分配内存。内存分配和释放由程序员控制,灵活但易出错(如内存泄漏、悬挂指针)。
- 全局/静态区(
Data/BSS Segment
):存储全局变量和静态变量。
了解栈和堆的区别,以及如何有效地在堆上分配和管理内存,是编写高效且安全的 C++ 程序的基础。
C 风格内存管理
malloc
函数
malloc
(memory allocation)用于在堆上分配指定字节数的内存。其原型如下:
#include <cstdlib>
void* malloc(size_t size);
- 参数:
size
- 要分配的内存字节数。 - 返回值:指向分配内存的指针,如果分配失败则返回
nullptr
。
free
函数
free
用于释放之前由 malloc
、calloc
或 realloc
分配的内存。其原型如下:
void free(void* ptr);
- 参数:
ptr
- 要释放的内存指针。
示例代码
#include <iostream>
#include <cstdlib> // 包含 malloc 和 free
int main() {
// 分配一个整数的内存
int* p = (int*)malloc(sizeof(int));
if (p == nullptr) {
std::cerr << "Memory allocation failed" << std::endl;
return 1;
}
*p = 42;
std::cout << "Value: " << *p << std::endl;
// 释放内存
free(p);
return 0;
}
注意事项
- 类型转换:
malloc
返回void*
,需要显式转换为所需类型的指针。 - 初始化:
malloc
分配的内存未初始化,内容不确定。 - 释放对应性:由
malloc
分配的内存必须使用free
释放,避免使用delete
。
C++ 内存管理
C++ 提供了更高层次的内存管理操作符:new
和 delete
,它们不仅分配和释放内存,还调用构造函数和析构函数,提供类型安全。
new
操作符
用于在堆上分配对象,并调用其构造函数。
单个对象
Type* ptr = new Type(parameters);
- 例子:
#include <iostream>
class MyClass {
public:
MyClass(int val) : value(val) {
std::cout << "Constructor called with value: " << value << std::endl;
}
~MyClass() {
std::cout << "Destructor called for value: " << value << std::endl;
}
int value;
};
int main() {
MyClass* obj = new MyClass(10);
std::cout << "Object value: " << obj->value << std::endl;
delete obj; // 调用析构函数并释放内存
return 0;
}
输出:
Constructor called with value: 10
Object value: 10
Destructor called for value: 10
数组
Type* array = new Type[size];
- 例子:
#include <iostream>
int main() {
int* arr = new int[5]; // 分配5个整数
for (int i = 0; i < 5; ++i) {
arr[i] = i * 10;
}
for (int i = 0; i < 5; ++i) {
std::cout << "arr[" << i << "] = " << arr[i] << std::endl;
}
delete[] arr; // 释放数组内存
return 0;
}
输出:
arr[0] = 0
arr[1] = 10
arr[2] = 20
arr[3] = 30
arr[4] = 40
delete
操作符
用于释放由 new
分配的内存,并调用析构函数。
释放单个对象
delete ptr;
释放数组
delete[] ptr;
区别于 malloc
和 free
- 类型安全:
new
返回正确类型的指针,免去了强制类型转换。 - 构造/析构:
new
和delete
自动调用构造函数和析构函数。 - 异常处理:在分配失败时,
new
默认抛出std::bad_alloc
异常,而malloc
返回nullptr
。
异常安全的 new
可以通过 nothrow
参数防止 new
抛出异常,改为返回 nullptr
。
#include <iostream>
#include <new> // 包含 std::nothrow
int main() {
int* p = new(std::nothrow) int;
if (p == nullptr) {
std::cerr << "Memory allocation failed" << std::endl;
return 1;
}
*p = 100;
std::cout << "Value: " << *p << std::endl;
delete p;
return 0;
}
总结和对比
了解 malloc/free
与 new/delete
的区别,有助于在编写 C++ 程序时正确选择内存管理方法。
特性 | malloc/free | new/delete |
---|---|---|
类型安全 | 需要显式类型转换 | 自动类型转换,无需显式转换 |
构造/析构函数 | 不调用对象的构造/析构函数 | 调用对象的构造/析构函数 |
返回值 | void* ,需要转换为目标类型 |
返回目标类型指针,类型安全 |
错误处理 | 分配失败返回 nullptr |
分配失败抛出 std::bad_alloc 异常 |
多态行为 | 无 | 支持多态,通过虚函数正确调用析构函数 |
内存分配与释放对应性 | 必须使用 free 释放由 malloc 分配的内存 |
必须使用 delete 释放由 new 分配的内存 |
示例对比
使用 malloc
和 free
#include <iostream>
#include <cstdlib>
class MyClass {
public:
MyClass(int val) : value(val) { std::cout << "Constructor called" << std::endl; }
~MyClass() { std::cout << "Destructor called" << std::endl; }
int value;
};
int main() {
// 使用 malloc 分配内存
MyClass* obj = (MyClass*)malloc(sizeof(MyClass));
if (obj == nullptr) {
std::cerr << "malloc failed" << std::endl;
return 1;
}
// 手动调用构造函数(不推荐)
new(obj) MyClass(20); // 通过“定位 new”调用构造函数
std::cout << "Value: " << obj->value << std::endl;
// 手动调用析构函数
obj->~MyClass();
// 释放内存
free(obj);
return 0;
}
注意:使用 malloc
分配 C++ 对象时,需要手动调用构造函数和析构函数,这非常不便且易出错。因此,推荐使用 new
和 delete
。
使用 new
和 delete
#include <iostream>
class MyClass {
public:
MyClass(int val) : value(val) { std::cout << "Constructor called" << std::endl; }
~MyClass() { std::cout << "Destructor called" << std::endl; }
int value;
};
int main() {
// 使用 new 分配内存并调用构造函数
MyClass* obj = new MyClass(30);
std::cout << "Value: " << obj->value << std::endl;
// 使用 delete 释放内存并调用析构函数
delete obj;
return 0;
}
输出:
Constructor called
Value: 30
Destructor called
兼容性
- C++ 类型特性:
new
和delete
支持 C++ 的类型特性,包括构造函数、析构函数、多态等。 - C 兼容性:在需要兼容 C 代码或通过 C 接口分配内存时,仍可能需要使用
malloc
和free
。
高级内存管理
使用 realloc 进行内存重分配
realloc
用于调整之前分配的内存块大小。这在动态数组等数据结构中非常有用。
原型
#include <cstdlib>
void* realloc(void* ptr, size_t new_size);
参数
:
ptr
:指向之前分配的内存块。new_size
:新的内存大小(以字节为单位)。
返回值:指向重新分配后的内存块的新指针。如果重新分配失败,返回
nullptr
,原内存块保持不变。
示例代码
#include <iostream>
#include <cstdlib>
#include <cstring> // 包含 memcpy
int main() {
// 初始分配 3 个整数
int* arr = (int*)malloc(3 * sizeof(int));
if (arr == nullptr) {
std::cerr << "Initial malloc failed" << std::endl;
return 1;
}
// 初始化数组
for (int i = 0; i < 3; ++i) {
arr[i] = i + 1;
}
std::cout << "Initial array: ";
for (int i = 0; i < 3; ++i) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
// 重新分配为 5 个整数
int* temp = (int*)realloc(arr, 5 * sizeof(int));
if (temp == nullptr) {
std::cerr << "Realloc failed" << std::endl;
free(arr); // 释放原内存
return 1;
}
arr = temp;
// 初始化新元素
for (int i = 3; i < 5; ++i) {
arr[i] = (i + 1) * 10;
}
std::cout << "Reallocated array: ";
for (int i = 0; i < 5; ++i) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
// 释放内存
free(arr);
return 0;
}
输出:
Initial array: 1 2 3
Reallocated array: 1 2 3 40 50
动态数组管理
使用 malloc
和 realloc
来手动管理动态数组可以实现可变大小的数组,但需要处理内存分配、释放和数据复制。
封装动态数组
#include <iostream>
#include <cstdlib>
#include <cstring>
class DynamicArray {
public:
DynamicArray(size_t initial_size = 1)
: size(initial_size), data((int*)malloc(size * sizeof(int))) {
if (data == nullptr) {
throw std::bad_alloc();
}
}
~DynamicArray() {
free(data);
}
void resize(size_t new_size) {
int* temp = (int*)realloc(data, new_size * sizeof(int));
if (temp == nullptr) {
throw std::bad_alloc();
}
data = temp;
size = new_size;
}
int& operator[](size_t index) {
return data[index];
}
size_t getSize() const { return size; }
private:
size_t size;
int* data;
};
int main() {
try {
DynamicArray arr(3);
// 初始化
for (size_t i = 0; i < arr.getSize(); ++i) {
arr[i] = i + 1;
}
std::cout << "Initial array: ";
for (size_t i = 0; i < arr.getSize(); ++i) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
// 调整大小
arr.resize(5);
arr[3] = 40;
arr[4] = 50;
std::cout << "Resized array: ";
for (size_t i = 0; i < arr.getSize(); ++i) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
}
catch (const std::bad_alloc& e) {
std::cerr << "Memory allocation error: " << e.what() << std::endl;
return 1;
}
return 0;
}
输出:
Initial array: 1 2 3
Resized array: 1 2 3 40 50
注意:这种方式需要手动管理内存和数组大小,且缺乏类型安全性和自动化。推荐使用 C++ 标准容器如 std::vector
来代替。
实际案例
案例一:动态数组实现
实现一个简单的动态数组类,支持添加元素、访问元素和自动扩展。
代码示例
#include <iostream>
#include <cstdlib>
#include <stdexcept>
class DynamicArray {
public:
DynamicArray()
: capacity(2), size(0), data((int*)malloc(capacity * sizeof(int))) {
if (data == nullptr) {
throw std::bad_alloc();
}
}
~DynamicArray() {
free(data);
}
void add(int value) {
if (size == capacity) {
resize(capacity * 2);
}
data[size++] = value;
}
int get(size_t index) const {
if (index >= size) {
throw std::out_of_range("Index out of range");
}
return data[index];
}
size_t getSize() const { return size; }
private:
void resize(size_t new_capacity) {
int* temp = (int*)realloc(data, new_capacity * sizeof(int));
if (temp == nullptr) {
throw std::bad_alloc();
}
data = temp;
capacity = new_capacity;
}
size_t capacity;
size_t size;
int* data;
};
int main() {
try {
DynamicArray arr;
arr.add(10);
arr.add(20);
arr.add(30); // 触发扩展
std::cout << "Dynamic Array Contents:" << std::endl;
for (size_t i = 0; i < arr.getSize(); ++i) {
std::cout << arr.get(i) << " ";
}
std::cout << std::endl;
}
catch (const std::bad_alloc& e) {
std::cerr << "Memory allocation error: " << e.what() << std::endl;
return 1;
}
catch (const std::out_of_range& e) {
std::cerr << "Array access error: " << e.what() << std::endl;
return 1;
}
return 0;
}
输出:
Dynamic Array Contents:
10 20 30
案例二:自定义内存管理器
实现一个简单的内存池,用于高效分配和释放固定大小的对象。
代码示例
#include <iostream>
#include <cstdlib>
#include <stack>
class MemoryPool {
public:
MemoryPool(size_t objectSize, size_t poolSize)
: objSize(objectSize), totalSize(poolSize), pool((char*)malloc(objectSize * poolSize)) {
if (pool == nullptr) {
throw std::bad_alloc();
}
// 初始化 free list
for (size_t i = 0; i < poolSize; ++i) {
freeList.push(pool + i * objectSize);
}
}
~MemoryPool() {
free(pool);
}
void* allocate() {
if (freeList.empty()) {
throw std::bad_alloc();
}
void* ptr = freeList.top();
freeList.pop();
return ptr;
}
void deallocate(void* ptr) {
freeList.push((char*)ptr);
}
private:
size_t objSize;
size_t totalSize;
char* pool;
std::stack<void*> freeList;
};
class MyClass {
public:
MyClass(int val) : value(val) {
std::cout << "MyClass constructor: " << value << std::endl;
}
~MyClass() {
std::cout << "MyClass destructor: " << value << std::endl;
}
int value;
};
int main() {
try {
// 创建一个能容纳 3 个 MyClass 对象的内存池
MemoryPool pool(sizeof(MyClass), 3);
// 分配对象内存
void* mem1 = pool.allocate();
void* mem2 = pool.allocate();
// 使用“定位 new”构造对象
MyClass* obj1 = new(mem1) MyClass(100);
MyClass* obj2 = new(mem2) MyClass(200);
// 使用对象
std::cout << "obj1 value: " << obj1->value << std::endl;
std::cout << "obj2 value: " << obj2->value << std::endl;
// 显式调用析构函数
obj1->~MyClass();
obj2->~MyClass();
// 释放内存
pool.deallocate(mem1);
pool.deallocate(mem2);
}
catch (const std::bad_alloc& e) {
std::cerr << "Memory pool allocation error: " << e.what() << std::endl;
return 1;
}
return 0;
}
输出:
MyClass constructor: 100
MyClass constructor: 200
obj1 value: 100
obj2 value: 200
MyClass destructor: 100
MyClass destructor: 200
说明:
- MemoryPool 管理固定大小的内存块,避免频繁调用
malloc
和free
。 - 使用“定位 new”在预分配的内存上构造对象。
- 需要手动调用析构函数和将内存返回给内存池。
注意:这种方法适用于大量小对象的高效管理,但需要确保正确使用构造和析构函数。
避免内存泄漏
内存泄漏是指程序分配的内存未被释放,导致内存被浪费,甚至耗尽。避免内存泄漏的策略包括:
- 确保每个
new
有对应的delete
。 - 使用
RAII
和智能指针:自动管理资源,避免手动管理内存。 - 工具辅助:使用工具如
Valgrind
检测内存泄漏。
示例:内存泄漏
#include <iostream>
int main() {
int* p = new int(10);
// 忘记 delete p; 导致内存泄漏
return 0;
}
解决方法:
#include <iostream>
int main() {
int* p = new int(10);
// 正确释放内存
delete p;
return 0;
}
RAII
(资源获取即初始化)
RAII
是 C++ 中的一种编程惯用法,通过对象的生命周期管理资源,确保资源在对象构造时获取,析构时释放,避免泄漏。
示例:RAII
实现类似于shared_ptr
智能指针
std::shared_ptr
是 C++ 标准库中功能强大的智能指针之一,提供了共享所有权的能力,使得多个指针可以共同管理同一个动态分配的对象。通过引用计数机制,shared_ptr
确保了对象在最后一个指针被销毁时自动释放,极大地简化了内存管理,防止了内存泄漏和悬挂指针问题。
SimpleSharedPtr
的基本概念
SimpleSharedPtr
是一个简化版的 shared_ptr
实现,旨在帮助理解其核心机制。其基本功能包括:
- 共享所有权:多个
SimpleSharedPtr
实例可以指向同一个对象,共享对该对象的所有权。 - 自动管理生命周期:当最后一个
SimpleSharedPtr
被销毁或指向其他对象时,管理的对象被自动释放。 - 引用计数:内部维护一个引用计数,记录有多少个
SimpleSharedPtr
实例指向同一个对象。
引用计数控制块的设计
为了实现引用计数机制,SimpleSharedPtr
需要一个控制块(Control Block),它包含:
- 引用计数(
ref_count
):记录有多少个SimpleSharedPtr
指向同一个对象。 - 指向对象的指针(
ptr
):指向实际管理的对象。
控制块通常与被管理对象一起被分配,但为了简化实现,本示例将它们独立管理。
struct ControlBlock {
int ref_count; // 引用计数
// 可以扩展为包含自定义删除器等
ControlBlock() : ref_count(1) {}
};
SimpleSharedPtr
的实现
类结构
SimpleSharedPtr
是一个模板类,模板参数 T
表示它所管理的对象类型。
template <typename T>
class SimpleSharedPtr {
private:
T* ptr; // 指向管理的对象
ControlBlock* control; // 指向控制块
public:
// 构造函数、析构函数、拷贝与移动操作、操作符重载等
};
构造函数与析构函数
- 默认构造函数:初始化指针和控制块为空。
- 参数化构造函数:接受一个裸指针,初始化控制块,并引用计数为1。
- 析构函数:减少引用计数,若引用计数为0,则释放对象和控制块。
// 默认构造函数
SimpleSharedPtr() : ptr(nullptr), control(nullptr) {}
// 参数化构造函数
explicit SimpleSharedPtr(T* p) : ptr(p) {
if (p) {
control = new ControlBlock();
} else {
control = nullptr;
}
}
// 析构函数
~SimpleSharedPtr() {
release();
}
辅助函数 release
:
private:
void release() {
if (control) {
control->ref_count--;
if (control->ref_count == 0) {
delete ptr;
delete control;
}
}
ptr = nullptr;
control = nullptr;
}
拷贝构造与拷贝赋值
拷贝构造函数和拷贝赋值操作符允许多个 SimpleSharedPtr
实例共享同一个对象,共享相同的控制块。
// 拷贝构造函数
SimpleSharedPtr(const SimpleSharedPtr& other) : ptr(other.ptr), control(other.control) {
if (control) {
control->ref_count++;
}
}
// 拷贝赋值操作符
SimpleSharedPtr& operator=(const SimpleSharedPtr& other) {
if (this != &other) {
// 释放当前资源
release();
// 复制新的资源和控制块
ptr = other.ptr;
control = other.control;
if (control) {
control->ref_count++;
}
}
return *this;
}
移动构造与移动赋值
移动语义允许资源所有权从一个 SimpleSharedPtr
转移到另一个,而不增加引用计数。
// 移动构造函数
SimpleSharedPtr(SimpleSharedPtr&& other) noexcept : ptr(other.ptr), control(other.control) {
other.ptr = nullptr;
other.control = nullptr;
}
// 移动赋值操作符
SimpleSharedPtr& operator=(SimpleSharedPtr&& other) noexcept {
if (this != &other) {
// 释放当前资源
release();
// 接管 `other` 的资源
ptr = other.ptr;
control = other.control;
// 置 `other` 为空
other.ptr = nullptr;
other.control = nullptr;
}
return *this;
}
操作符重载
重载 *
和 ->
操作符,以便像使用原生指针一样使用 SimpleSharedPtr
。
// 解引用操作符
T& operator*() const {
return *ptr;
}
// 箭头操作符
T* operator->() const {
return ptr;
}
其他成员函数
use_count
:返回当前引用计数。get
:返回裸指针。reset
:重置指针,指向新对象或nullptr
。
// 获取引用计数
int use_count() const {
return control ? control->ref_count : 0;
}
// 获取裸指针
T* get() const {
return ptr;
}
// 重置指针
void reset(T* p = nullptr) {
// 释放当前资源
release();
// 指向新资源
ptr = p;
if (p) {
control = new ControlBlock();
} else {
control = nullptr;
}
}
完整代码示例
以下是 SimpleSharedPtr
的完整实现及其使用示例。
#include <iostream>
// 控制块结构
struct ControlBlock {
int ref_count;
ControlBlock() : ref_count(1) {}
};
// 简化版的 shared_ptr 实现
template <typename T>
class SimpleSharedPtr {
private:
T* ptr; // 指向管理的对象
ControlBlock* control; // 指向控制块
// 释放当前资源
void release() {
if (control) {
control->ref_count--;
std::cout << "Decremented ref_count to " << control->ref_count << std::endl;
if (control->ref_count == 0) {
delete ptr;
delete control;
std::cout << "Resource and ControlBlock destroyed." << std::endl;
}
}
ptr = nullptr;
control = nullptr;
}
public:
// 默认构造函数
SimpleSharedPtr() : ptr(nullptr), control(nullptr) {
std::cout << "Default constructed SimpleSharedPtr (nullptr)." << std::endl;
}
// 参数化构造函数
explicit SimpleSharedPtr(T* p) : ptr(p) {
if (p) {
control = new ControlBlock();
std::cout << "Constructed SimpleSharedPtr, ref_count = " << control->ref_count << std::endl;
} else {
control = nullptr;
}
}
// 拷贝构造函数
SimpleSharedPtr(const SimpleSharedPtr& other) : ptr(other.ptr), control(other.control) {
if (control) {
control->ref_count++;
std::cout << "Copied SimpleSharedPtr, ref_count = " << control->ref_count << std::endl;
}
}
// 拷贝赋值操作符
SimpleSharedPtr& operator=(const SimpleSharedPtr& other) {
if (this != &other) {
release();
ptr = other.ptr;
control = other.control;
if (control) {
control->ref_count++;
std::cout << "Assigned SimpleSharedPtr, ref_count = " << control->ref_count << std::endl;
}
}
return *this;
}
// 移动构造函数
SimpleSharedPtr(SimpleSharedPtr&& other) noexcept : ptr(other.ptr), control(other.control) {
other.ptr = nullptr;
other.control = nullptr;
std::cout << "Moved SimpleSharedPtr." << std::endl;
}
// 移动赋值操作符
SimpleSharedPtr& operator=(SimpleSharedPtr&& other) noexcept {
if (this != &other) {
release();
ptr = other.ptr;
control = other.control;
other.ptr = nullptr;
other.control = nullptr;
std::cout << "Move-assigned SimpleSharedPtr." << std::endl;
}
return *this;
}
// 析构函数
~SimpleSharedPtr() {
release();
}
// 解引用操作符
T& operator*() const {
return *ptr;
}
// 箭头操作符
T* operator->() const {
return ptr;
}
// 获取引用计数
int use_count() const {
return control ? control->ref_count : 0;
}
// 获取裸指针
T* get() const {
return ptr;
}
// 重置指针
void reset(T* p = nullptr) {
release();
ptr = p;
if (p) {
control = new ControlBlock();
std::cout << "Reset SimpleSharedPtr, ref_count = " << control->ref_count << std::endl;
} else {
control = nullptr;
}
}
};
// 测试类
class Test {
public:
Test(int val) : value(val) {
std::cout << "Test Constructor: " << value << std::endl;
}
~Test() {
std::cout << "Test Destructor: " << value << std::endl;
}
void show() const {
std::cout << "Value: " << value << std::endl;
}
private:
int value;
};
int main() {
std::cout << "Creating default constructed shared_ptr..." << std::endl;
SimpleSharedPtr<Test> ptr1; // 默认构造
std::cout << "ptr1 use_count: " << ptr1.use_count() << std::endl;
std::cout << "\nCreating shared_ptr with resource..." << std::endl;
SimpleSharedPtr<Test> ptr2(new Test(100)); // 非默认构造
std::cout << "ptr2 use_count: " << ptr2.use_count() << std::endl;
ptr2->show();
std::cout << "\nCopying ptr2 to ptr3..." << std::endl;
SimpleSharedPtr<Test> ptr3 = ptr2; // 拷贝构造
std::cout << "ptr2 use_count: " << ptr2.use_count() << std::endl;
std::cout << "ptr3 use_count: " << ptr3.use_count() << std::endl;
ptr3->show();
std::cout << "\nAssigning ptr3 to ptr1..." << std::endl;
ptr1 = ptr3; // 拷贝赋值
std::cout << "ptr1 use_count: " << ptr1.use_count() << std::endl;
std::cout << "ptr2 use_count: " << ptr2.use_count() << std::endl;
std::cout << "ptr3 use_count: " << ptr3.use_count() << std::endl;
std::cout << "\nResetting ptr2..." << std::endl;
ptr2.reset(new Test(200)); // 重新指向新的对象
std::cout << "ptr2 use_count: " << ptr2.use_count() << std::endl;
ptr2->show();
std::cout << "ptr1 use_count: " << ptr1.use_count() << std::endl;
std::cout << "ptr3 use_count: " << ptr3.use_count() << std::endl;
std::cout << "\nExiting scope..." << std::endl;
} // ptr2, ptr1, ptr3 离开作用域
std::cout << "End of main." << std::endl;
return 0;
}
SimpleUniquePtr
的实现
std::unique_ptr
是一种独占所有权的智能指针,确保在任意时刻,只有一个 unique_ptr
实例指向特定资源。它不支持拷贝操作,只支持移动操作。
基本结构
首先,定义一个模板类 SimpleUniquePtr
,它持有一个指向资源的裸指针:
template <typename T>
class SimpleUniquePtr {
private:
T* ptr; // 指向管理对象的裸指针
public:
// 构造函数、析构函数、删除拷贝构造与拷贝赋值
// 实现移动构造与移动赋值
// 重载操作符
};
构造函数与析构函数
- 默认构造函数:初始化指针为空。
- 参数化构造函数:接受一个指向资源的裸指针。
- 析构函数:当
SimpleUniquePtr
被销毁时,释放所管理的资源。
// 默认构造函数
SimpleUniquePtr() : ptr(nullptr) {}
// 参数化构造函数
explicit SimpleUniquePtr(T* p) : ptr(p) {}
// 析构函数
~SimpleUniquePtr() {
delete ptr;
}
删除拷贝构造与拷贝赋值
为了确保唯一性,禁止拷贝构造和拷贝赋值:
// 删除拷贝构造
SimpleUniquePtr(const SimpleUniquePtr&) = delete;
// 删除拷贝赋值
SimpleUniquePtr& operator=(const SimpleUniquePtr&) = delete;
移动语义
支持移动构造和移动赋值,以转移所有权:
// 移动构造
SimpleUniquePtr(SimpleUniquePtr&& other) noexcept : ptr(other.ptr) {
other.ptr = nullptr;
}
// 移动赋值
SimpleUniquePtr& operator=(SimpleUniquePtr&& other) noexcept {
if (this != &other) {
delete ptr; // 释放当前资源
ptr = other.ptr; // 转移所有权
other.ptr = nullptr;
}
return *this;
}
操作符重载
重载 *
和 ->
操作符,以模拟指针的行为:
// 解引用操作符
T& operator*() const {
return *ptr;
}
// 箭头操作符
T* operator->() const {
return ptr;
}
// 获取裸指针
T* get() const {
return ptr;
}
// 释放所有权,返回裸指针并设为 nullptr
T* release() {
T* temp = ptr;
ptr = nullptr;
return temp;
}
// 重新设定指针
void reset(T* p = nullptr) {
delete ptr;
ptr = p;
}
示例代码
以下示例展示了如何使用 SimpleUniquePtr
:
#include <iostream>
// SimpleUniquePtr 实现
template <typename T>
class SimpleUniquePtr {
private:
T* ptr;
public:
// 默认构造函数
SimpleUniquePtr() : ptr(nullptr) {}
// 参数化构造函数
explicit SimpleUniquePtr(T* p) : ptr(p) {}
// 析构函数
~SimpleUniquePtr() {
delete ptr;
}
// 删除拷贝构造和拷贝赋值
SimpleUniquePtr(const SimpleUniquePtr&) = delete;
SimpleUniquePtr& operator=(const SimpleUniquePtr&) = delete;
// 移动构造
SimpleUniquePtr(SimpleUniquePtr&& other) noexcept : ptr(other.ptr) {
other.ptr = nullptr;
}
// 移动赋值
SimpleUniquePtr& operator=(SimpleUniquePtr&& other) noexcept {
if (this != &other) {
delete ptr;
ptr = other.ptr;
other.ptr = nullptr;
}
return *this;
}
// 解引用操作符
T& operator*() const {
return *ptr;
}
// 箭头操作符
T* operator->() const {
return ptr;
}
// 获取裸指针
T* get() const {
return ptr;
}
// 释放所有权
T* release() {
T* temp = ptr;
ptr = nullptr;
return temp;
}
// 重新设定指针
void reset(T* p = nullptr) {
delete ptr;
ptr = p;
}
};
// 测试类
class Test {
public:
Test(int val) : value(val) {
std::cout << "Test Constructor: " << value << std::endl;
}
~Test() {
std::cout << "Test Destructor: " << value << std::endl;
}
void show() const {
std::cout << "Value: " << value << std::endl;
}
private:
int value;
};
int main() {
// 创建一个 SimpleUniquePtr
SimpleUniquePtr<Test> ptr1(new Test(1));
ptr1->show();
(*ptr1).show();
// 移动所有权到 ptr2
SimpleUniquePtr<Test> ptr2 = std::move(ptr1);
if (ptr1.get() == nullptr) {
std::cout << "ptr1 is now nullptr after move." << std::endl;
}
ptr2->show();
// 释放所有权
Test* rawPtr = ptr2.release();
if (ptr2.get() == nullptr) {
std::cout << "ptr2 is now nullptr after release." << std::endl;
}
rawPtr->show();
delete rawPtr; // 手动删除
// 使用 reset
ptr2.reset(new Test(2));
ptr2->show();
ptr2.reset(); // 自动删除
if (ptr2.get() == nullptr) {
std::cout << "ptr2 is now nullptr after reset." << std::endl;
}
return 0;
}
输出:
Test Constructor: 1
Value: 1
Value: 1
ptr1 is now nullptr after move.
Value: 1
ptr2 is now nullptr after release.
Value: 1
Test Destructor: 1
Test Constructor: 2
Value: 2
Test Destructor: 2
ptr2 is now nullptr after reset.
解释:
- 创建
ptr1
并指向一个Test
对象。 - 使用
std::move
将所有权转移到ptr2
,ptr1
变为nullptr
。 - 使用
release()
释放ptr2
的所有权,获取裸指针后需要手动delete
。 - 使用
reset()
重新指向一个新的Test
对象,自动释放之前的资源。
总结
源码连接
https://gitee.com/secondtonone1/boostasio-learn/tree/master/base/day22-memory
本文详细介绍了 C++ 中的内存管理方法,包括基础的 malloc
和 free
,以及更现代的 C++ 风格的 new
和 delete
。通过对比两者的特点,强调了 new
和 delete
在 C++ 中的优势,如类型安全、自动调用构造和析构函数等。
高级内存管理部分探讨了如何使用 realloc
进行内存重分配,并通过实际案例展示了如何实现动态数组和自定义内存管理器。最后,介绍了最佳实践,强调避免内存泄漏的重要性,以及 RAII
和智能指针对内存管理的帮助。