零基础C++(22) 内存管理

内存管理简介

详细技术视频请看我的主页

C++教程视频

C++ 提供了多种内存管理方式,包括传统的 C 风格的 mallocfree,以及 C++ 专用的 newdelete

理解这些内存管理方法对于编写高效、安全的 C++ 程序至关重要。

本文将详细介绍这些内存管理方法,包含基本用法、复杂操作(如 realloc),并配以实际案例和代码示例。

内存管理基础

在 C++ 程序中,内存主要分为以下几个区域:

  • 栈(Stack):自动管理内存,存储局部变量和函数调用信息。内存分配和释放速度快,但空间有限。
  • 堆(Heap):手动管理内存,用于动态分配内存。内存分配和释放由程序员控制,灵活但易出错(如内存泄漏、悬挂指针)。
  • 全局/静态区(Data/BSS Segment:存储全局变量和静态变量。

了解栈和堆的区别,以及如何有效地在堆上分配和管理内存,是编写高效且安全的 C++ 程序的基础。

C 风格内存管理

malloc 函数

malloc(memory allocation)用于在堆上分配指定字节数的内存。其原型如下:

1
2
3
#include <cstdlib>

void* malloc(size_t size);
  • 参数size - 要分配的内存字节数。
  • 返回值:指向分配内存的指针,如果分配失败则返回 nullptr

free 函数

free 用于释放之前由 malloccallocrealloc 分配的内存。其原型如下:

1
void free(void* ptr);
  • 参数ptr - 要释放的内存指针。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#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++ 提供了更高层次的内存管理操作符:newdelete,它们不仅分配和释放内存,还调用构造函数和析构函数,提供类型安全。

new 操作符

用于在堆上分配对象,并调用其构造函数。

单个对象

1
Type* ptr = new Type(parameters);
  • 例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#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;
}

输出

1
2
3
Constructor called with value: 10
Object value: 10
Destructor called for value: 10

数组

1
Type* array = new Type[size];
  • 例子
1
2
3
4
5
6
7
8
9
10
11
12
13
#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;
}

输出

1
2
3
4
5
arr[0] = 0
arr[1] = 10
arr[2] = 20
arr[3] = 30
arr[4] = 40

delete 操作符

用于释放由 new 分配的内存,并调用析构函数。

释放单个对象

1
delete ptr;

释放数组

1
delete[] ptr;

区别于 mallocfree

  • 类型安全new 返回正确类型的指针,免去了强制类型转换。
  • 构造/析构newdelete 自动调用构造函数和析构函数。
  • 异常处理:在分配失败时,new 默认抛出 std::bad_alloc 异常,而 malloc 返回 nullptr

异常安全的 new

可以通过 nothrow 参数防止 new 抛出异常,改为返回 nullptr

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#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/freenew/delete 的区别,有助于在编写 C++ 程序时正确选择内存管理方法。

特性 malloc/free new/delete
类型安全 需要显式类型转换 自动类型转换,无需显式转换
构造/析构函数 不调用对象的构造/析构函数 调用对象的构造/析构函数
返回值 void*,需要转换为目标类型 返回目标类型指针,类型安全
错误处理 分配失败返回 nullptr 分配失败抛出 std::bad_alloc 异常
多态行为 支持多态,通过虚函数正确调用析构函数
内存分配与释放对应性 必须使用 free 释放由 malloc 分配的内存 必须使用 delete 释放由 new 分配的内存

示例对比

使用 mallocfree

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#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++ 对象时,需要手动调用构造函数和析构函数,这非常不便且易出错。因此,推荐使用 newdelete

使用 newdelete

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#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;
}

输出

1
2
3
Constructor called
Value: 30
Destructor called

兼容性

  • C++ 类型特性newdelete 支持 C++ 的类型特性,包括构造函数、析构函数、多态等。
  • C 兼容性:在需要兼容 C 代码或通过 C 接口分配内存时,仍可能需要使用 mallocfree

高级内存管理

使用 realloc 进行内存重分配

realloc 用于调整之前分配的内存块大小。这在动态数组等数据结构中非常有用。

原型

1
2
3
#include <cstdlib>

void* realloc(void* ptr, size_t new_size);
  • 参数

    • ptr:指向之前分配的内存块。
    • new_size:新的内存大小(以字节为单位)。
  • 返回值:指向重新分配后的内存块的新指针。如果重新分配失败,返回 nullptr,原内存块保持不变。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#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;
}

输出

1
2
Initial array: 1 2 3 
Reallocated array: 1 2 3 40 50

动态数组管理

使用 mallocrealloc 来手动管理动态数组可以实现可变大小的数组,但需要处理内存分配、释放和数据复制。

封装动态数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#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;
}

输出

1
2
Initial array: 1 2 3 
Resized array: 1 2 3 40 50

注意:这种方式需要手动管理内存和数组大小,且缺乏类型安全性和自动化。推荐使用 C++ 标准容器如 std::vector 来代替。

实际案例

案例一:动态数组实现

实现一个简单的动态数组类,支持添加元素、访问元素和自动扩展。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#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;
}

输出

1
2
Dynamic Array Contents:
10 20 30

案例二:自定义内存管理器

实现一个简单的内存池,用于高效分配和释放固定大小的对象。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#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;
}

输出

1
2
3
4
5
6
MyClass constructor: 100
MyClass constructor: 200
obj1 value: 100
obj2 value: 200
MyClass destructor: 100
MyClass destructor: 200

说明

  • MemoryPool 管理固定大小的内存块,避免频繁调用 mallocfree
  • 使用“定位 new”在预分配的内存上构造对象。
  • 需要手动调用析构函数和将内存返回给内存池。

注意:这种方法适用于大量小对象的高效管理,但需要确保正确使用构造和析构函数。

避免内存泄漏

内存泄漏是指程序分配的内存未被释放,导致内存被浪费,甚至耗尽。避免内存泄漏的策略包括:

  • **确保每个 new 有对应的 delete**。
  • 使用 RAII 和智能指针:自动管理资源,避免手动管理内存。
  • 工具辅助:使用工具如 Valgrind 检测内存泄漏。

示例:内存泄漏

1
2
3
4
5
6
7
#include <iostream>

int main() {
int* p = new int(10);
// 忘记 delete p; 导致内存泄漏
return 0;
}

解决方法

1
2
3
4
5
6
7
8
#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:指向实际管理的对象。

控制块通常与被管理对象一起被分配,但为了简化实现,本示例将它们独立管理。

1
2
3
4
5
6
struct ControlBlock {
int ref_count; // 引用计数
// 可以扩展为包含自定义删除器等

ControlBlock() : ref_count(1) {}
};

SimpleSharedPtr 的实现

类结构

SimpleSharedPtr 是一个模板类,模板参数 T 表示它所管理的对象类型。

1
2
3
4
5
6
7
8
9
template <typename T>
class SimpleSharedPtr {
private:
T* ptr; // 指向管理的对象
ControlBlock* control; // 指向控制块

public:
// 构造函数、析构函数、拷贝与移动操作、操作符重载等
};

构造函数与析构函数

  • 默认构造函数:初始化指针和控制块为空。
  • 参数化构造函数:接受一个裸指针,初始化控制块,并引用计数为1。
  • 析构函数:减少引用计数,若引用计数为0,则释放对象和控制块。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 默认构造函数
SimpleSharedPtr() : ptr(nullptr), control(nullptr) {}

// 参数化构造函数
explicit SimpleSharedPtr(T* p) : ptr(p) {
if (p) {
control = new ControlBlock();
} else {
control = nullptr;
}
}

// 析构函数
~SimpleSharedPtr() {
release();
}

**辅助函数 release**:

1
2
3
4
5
6
7
8
9
10
11
12
private:
void release() {
if (control) {
control->ref_count--;
if (control->ref_count == 0) {
delete ptr;
delete control;
}
}
ptr = nullptr;
control = nullptr;
}

拷贝构造与拷贝赋值

拷贝构造函数和拷贝赋值操作符允许多个 SimpleSharedPtr 实例共享同一个对象,共享相同的控制块。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 拷贝构造函数
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 转移到另一个,而不增加引用计数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 移动构造函数
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

1
2
3
4
5
6
7
8
9
// 解引用操作符
T& operator*() const {
return *ptr;
}

// 箭头操作符
T* operator->() const {
return ptr;
}

其他成员函数

  • **use_count**:返回当前引用计数。
  • **get**:返回裸指针。
  • **reset**:重置指针,指向新对象或 nullptr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 获取引用计数
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 的完整实现及其使用示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
#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,它持有一个指向资源的裸指针:

1
2
3
4
5
6
7
8
9
10
template <typename T>
class SimpleUniquePtr {
private:
T* ptr; // 指向管理对象的裸指针

public:
// 构造函数、析构函数、删除拷贝构造与拷贝赋值
// 实现移动构造与移动赋值
// 重载操作符
};

构造函数与析构函数

  • 默认构造函数:初始化指针为空。
  • 参数化构造函数:接受一个指向资源的裸指针。
  • 析构函数:当 SimpleUniquePtr 被销毁时,释放所管理的资源。
1
2
3
4
5
6
7
8
9
10
// 默认构造函数
SimpleUniquePtr() : ptr(nullptr) {}

// 参数化构造函数
explicit SimpleUniquePtr(T* p) : ptr(p) {}

// 析构函数
~SimpleUniquePtr() {
delete ptr;
}

删除拷贝构造与拷贝赋值

为了确保唯一性,禁止拷贝构造和拷贝赋值:

1
2
3
4
5
// 删除拷贝构造
SimpleUniquePtr(const SimpleUniquePtr&) = delete;

// 删除拷贝赋值
SimpleUniquePtr& operator=(const SimpleUniquePtr&) = delete;

移动语义

支持移动构造和移动赋值,以转移所有权:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 移动构造
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;
}

操作符重载

重载 *-> 操作符,以模拟指针的行为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 解引用操作符
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#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;
}

输出

1
2
3
4
5
6
7
8
9
10
11
12
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 将所有权转移到 ptr2ptr1 变为 nullptr
  • 使用 release() 释放 ptr2 的所有权,获取裸指针后需要手动 delete
  • 使用 reset() 重新指向一个新的 Test 对象,自动释放之前的资源。

总结

本文详细介绍了 C++ 中的内存管理方法,包括基础的 mallocfree,以及更现代的 C++ 风格的 newdelete。通过对比两者的特点,强调了 newdelete 在 C++ 中的优势,如类型安全、自动调用构造和析构函数等。

高级内存管理部分探讨了如何使用 realloc 进行内存重分配,并通过实际案例展示了如何实现动态数组和自定义内存管理器。最后,介绍了最佳实践,强调避免内存泄漏的重要性,以及 RAII 和智能指针对内存管理的帮助。