第17章 类的内存管理
动态内存分配概述
动态内存分配是指在程序运行时根据需要分配和释放内存的过程。在C++中,使用new和delete运算符进行动态内存管理。
基本概念
- 静态内存:在编译时分配的内存,如全局变量、静态变量
- 栈内存:在函数调用时自动分配的内存,函数返回时自动释放
- 堆内存:在运行时动态分配的内存,需要手动释放
动态内存分配的优点
- 灵活性:可以根据运行时的需要分配内存
- 持久性:堆内存的生命周期由程序员控制,不受函数作用域的限制
- 大数据结构:可以分配大型数据结构,如大型数组、复杂对象等
new 和 delete 运算符
分配单个对象
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
| int* pInt = new int; *pInt = 100; std::cout << *pInt << std::endl; delete pInt; pInt = nullptr;
int* pInt2 = new int(200); std::cout << *pInt2 << std::endl; delete pInt2; pInt2 = nullptr;
class MyClass { private: int value;
public: MyClass(int v) : value(v) { std::cout << "Constructor called" << std::endl; } ~MyClass() { std::cout << "Destructor called" << std::endl; } int getValue() const { return value; } };
MyClass* pObj = new MyClass(300); std::cout << pObj->getValue() << std::endl; delete pObj; pObj = 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
| int* pArray = new int[5]; for (int i = 0; i < 5; i++) { pArray[i] = i; } for (int i = 0; i < 5; i++) { std::cout << pArray[i] << " "; } std::cout << std::endl; delete[] pArray; pArray = nullptr;
int* pArray2 = new int[5]{1, 2, 3, 4, 5}; for (int i = 0; i < 5; i++) { std::cout << pArray2[i] << " "; } std::cout << std::endl; delete[] pArray2; pArray2 = nullptr;
MyClass* pObjArray = new MyClass[3]{10, 20, 30}; for (int i = 0; i < 3; i++) { std::cout << pObjArray[i].getValue() << " "; } std::cout << std::endl; delete[] pObjArray; pObjArray = nullptr;
|
分配失败
1 2 3 4 5 6 7 8 9
| int* pInt = new (std::nothrow) int[1000000000]; if (pInt == nullptr) { std::cout << "Memory allocation failed" << std::endl; } else { std::cout << "Memory allocation successful" << std::endl; delete[] pInt; pInt = 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
| class String { private: char* data; size_t length;
public: String(const char* str) { if (str) { length = strlen(str); data = new char[length + 1]; strcpy(data, str); } else { length = 0; data = new char[1]; data[0] = '\0'; } std::cout << "Constructor called" << std::endl; } ~String() { delete[] data; std::cout << "Destructor called" << std::endl; } const char* c_str() const { return data; } size_t size() const { return length; } };
int main() { String s1("Hello"); std::cout << "s1: " << s1.c_str() << ", size: " << s1.size() << std::endl; String s2("World"); std::cout << "s2: " << s2.c_str() << ", size: " << s2.size() << std::endl; return 0; }
|
拷贝构造函数
当类包含指针成员时,需要实现拷贝构造函数以避免浅拷贝问题。
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
| class String { private: char* data; size_t length;
public: String(const char* str) { if (str) { length = strlen(str); data = new char[length + 1]; strcpy(data, str); } else { length = 0; data = new char[1]; data[0] = '\0'; } std::cout << "Constructor called" << std::endl; } String(const String& other) { length = other.length; data = new char[length + 1]; strcpy(data, other.data); std::cout << "Copy constructor called" << std::endl; } ~String() { delete[] data; std::cout << "Destructor called" << std::endl; } const char* c_str() const { return data; } size_t size() const { return length; } };
int main() { String s1("Hello"); String s2 = s1; std::cout << "s1: " << s1.c_str() << std::endl; std::cout << "s2: " << s2.c_str() << std::endl; return 0; }
|
赋值运算符
当类包含指针成员时,需要重载赋值运算符以避免浅拷贝问题。
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
| class String { private: char* data; size_t length;
public: String(const char* str) { if (str) { length = strlen(str); data = new char[length + 1]; strcpy(data, str); } else { length = 0; data = new char[1]; data[0] = '\0'; } std::cout << "Constructor called" << std::endl; } String(const String& other) { length = other.length; data = new char[length + 1]; strcpy(data, other.data); std::cout << "Copy constructor called" << std::endl; } String& operator=(const String& other) { if (this != &other) { delete[] data; length = other.length; data = new char[length + 1]; strcpy(data, other.data); std::cout << "Assignment operator called" << std::endl; } return *this; } ~String() { delete[] data; std::cout << "Destructor called" << std::endl; } const char* c_str() const { return data; } size_t size() const { return length; } };
int main() { String s1("Hello"); String s2("World"); std::cout << "Before assignment:" << std::endl; std::cout << "s1: " << s1.c_str() << std::endl; std::cout << "s2: " << s2.c_str() << std::endl; s2 = s1; std::cout << "After assignment:" << std::endl; std::cout << "s1: " << s1.c_str() << std::endl; std::cout << "s2: " << s2.c_str() << std::endl; return 0; }
|
移动构造函数和移动赋值运算符(C++11+)
为了提高性能,对于包含动态内存的类,应该实现移动构造函数和移动赋值运算符。
移动构造函数
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
| class String { private: char* data; size_t length;
public: String(const char* str) { if (str) { length = strlen(str); data = new char[length + 1]; strcpy(data, str); } else { length = 0; data = new char[1]; data[0] = '\0'; } std::cout << "Constructor called" << std::endl; } String(const String& other) { length = other.length; data = new char[length + 1]; strcpy(data, other.data); std::cout << "Copy constructor called" << std::endl; } String(String&& other) noexcept : data(other.data), length(other.length) { other.data = nullptr; other.length = 0; std::cout << "Move constructor called" << std::endl; } String& operator=(const String& other) { if (this != &other) { delete[] data; length = other.length; data = new char[length + 1]; strcpy(data, other.data); std::cout << "Assignment operator called" << std::endl; } return *this; } String& operator=(String&& other) noexcept { if (this != &other) { delete[] data; data = other.data; length = other.length; other.data = nullptr; other.length = 0; std::cout << "Move assignment operator called" << std::endl; } return *this; } ~String() { delete[] data; std::cout << "Destructor called" << std::endl; } const char* c_str() const { return data; } size_t size() const { return length; } };
String createString() { return String("Temporary string"); }
int main() { String s1("Hello"); String s2 = std::move(s1); std::cout << "s2: " << s2.c_str() << std::endl; String s3("World"); s3 = std::move(s2); std::cout << "s3: " << s3.c_str() << std::endl; String s4 = createString(); std::cout << "s4: " << s4.c_str() << std::endl; return 0; }
|
C++20新特性:std::span
C++20引入了std::span,用于安全地访问数组和其他序列,而不需要拥有它们的所有权:
std::span的基本概念
- span:一个非拥有的序列视图,包含指针和大小
- 灵活性:可以指向任何连续的内存区域,如数组、vector、string等
- 安全性:提供边界检查,避免越界访问
- 零开销:span本身不分配内存,只是对现有内存的视图
std::span的使用示例
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
| #include <span> #include <iostream> #include <vector> #include <array>
void processArray(std::span<int> numbers) { for (size_t i = 0; i < numbers.size(); i++) { numbers[i] *= 2; } }
void printData(std::span<const int> data) { for (int value : data) { std::cout << value << " "; } std::cout << std::endl; }
int main() { int arr[] = {1, 2, 3, 4, 5}; std::span<int> arrSpan(arr); processArray(arrSpan); printData(arrSpan); std::vector<int> vec = {10, 20, 30, 40, 50}; std::span<int> vecSpan(vec); processArray(vecSpan); printData(vecSpan); std::array<int, 3> arr3 = {100, 200, 300}; std::span<int> arr3Span(arr3); processArray(arr3Span); printData(arr3Span); std::span<int> subSpan = arrSpan.subspan(1, 3); printData(subSpan); return 0; }
|
std::span的优点
- 安全性:提供边界检查,避免越界访问
- 灵活性:可以指向任何连续的内存区域
- 零开销:不分配内存,只是对现有内存的视图
- 简洁性:简化了函数参数,不再需要同时传递指针和大小
- 兼容性:与现有的数组和容器无缝集成
智能指针
智能指针是C++11引入的一种自动管理动态内存的工具,它们会在适当的时候自动释放所管理的内存。
unique_ptr
unique_ptr是一种独占所有权的智能指针,同一时间只能有一个unique_ptr指向同一个对象。
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
| #include <memory>
class MyClass { private: int value;
public: MyClass(int v) : value(v) { std::cout << "Constructor called with value: " << value << std::endl; } ~MyClass() { std::cout << "Destructor called with value: " << value << std::endl; } int getValue() const { return value; } };
int main() { std::unique_ptr<MyClass> p1(new MyClass(100)); std::cout << "p1: " << p1->getValue() << std::endl; auto p2 = std::make_unique<MyClass>(200); std::cout << "p2: " << p2->getValue() << std::endl; std::unique_ptr<MyClass> p3 = std::move(p1); if (p1) { std::cout << "p1 is not null" << std::endl; } else { std::cout << "p1 is null" << std::endl; } std::cout << "p3: " << p3->getValue() << std::endl; auto pArray = std::make_unique<MyClass[]>(3); return 0; }
|
shared_ptr
shared_ptr是一种共享所有权的智能指针,多个shared_ptr可以指向同一个对象,当最后一个shared_ptr被销毁时,对象才会被释放。
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
| #include <memory>
class MyClass { private: int value;
public: MyClass(int v) : value(v) { std::cout << "Constructor called with value: " << value << std::endl; } ~MyClass() { std::cout << "Destructor called with value: " << value << std::endl; } int getValue() const { return value; } };
int main() { std::shared_ptr<MyClass> p1(new MyClass(100)); std::cout << "p1 use count: " << p1.use_count() << std::endl; auto p2 = std::make_shared<MyClass>(200); std::cout << "p2 use count: " << p2.use_count() << std::endl; std::shared_ptr<MyClass> p3 = p1; std::cout << "p1 use count after p3 assignment: " << p1.use_count() << std::endl; std::cout << "p3 use count: " << p3.use_count() << std::endl; p1.reset(); std::cout << "p1 use count after reset: " << (p1 ? p1.use_count() : 0) << std::endl; std::cout << "p3 use count after p1 reset: " << p3.use_count() << std::endl; return 0; }
|
weak_ptr
weak_ptr是一种不增加引用计数的智能指针,用于解决shared_ptr的循环引用问题。
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
| #include <memory>
class B;
class A { private: std::shared_ptr<B> bPtr;
public: A() { std::cout << "A constructor called" << std::endl; } ~A() { std::cout << "A destructor called" << std::endl; } void setB(const std::shared_ptr<B>& b) { bPtr = b; } };
class B { private: std::weak_ptr<A> aPtr;
public: B() { std::cout << "B constructor called" << std::endl; } ~B() { std::cout << "B destructor called" << std::endl; } void setA(const std::shared_ptr<A>& a) { aPtr = a; } };
int main() { std::shared_ptr<A> a = std::make_shared<A>(); std::shared_ptr<B> b = std::make_shared<B>(); a->setB(b); b->setA(a); std::cout << "a use count: " << a.use_count() << std::endl; std::cout << "b use count: " << b.use_count() << std::endl; if (auto aLocked = b->aPtr.lock()) { std::cout << "a is still valid" << std::endl; } else { std::cout << "a is no longer valid" << std::endl; } return 0; }
|
异常安全
异常安全的概念
异常安全是指当程序抛出异常时,系统能够保持一致的状态,不会泄漏资源,也不会破坏数据结构。
异常安全级别
- 无抛出保证(No-throw guarantee):函数不会抛出异常
- 强异常安全保证(Strong exception safety):如果函数抛出异常,程序状态不变
- 基本异常安全保证(Basic exception safety):如果函数抛出异常,程序状态保持有效,但可能与之前不同
- 无异常安全保证(No exception safety):函数抛出异常时,程序可能处于无效状态
实现异常安全的类
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
| class String { private: char* data; size_t length;
public: String(const char* str) : data(nullptr), length(0) { if (str) { length = strlen(str); char* temp = new char[length + 1]; strcpy(temp, str); data = temp; } else { char* temp = new char[1]; temp[0] = '\0'; data = temp; } std::cout << "Constructor called" << std::endl; } String(const String& other) : data(nullptr), length(0) { char* temp = new char[other.length + 1]; strcpy(temp, other.data); data = temp; length = other.length; std::cout << "Copy constructor called" << std::endl; } String& operator=(String other) { swap(other); std::cout << "Assignment operator called" << std::endl; return *this; } void swap(String& other) noexcept { std::swap(data, other.data); std::swap(length, other.length); } ~String() { delete[] data; std::cout << "Destructor called" << std::endl; } const char* c_str() const { return data; } size_t size() const { return length; } };
|
动态内存管理的最佳实践
1. 使用智能指针
优先使用std::unique_ptr和std::shared_ptr管理动态内存,避免手动使用new和delete。
2. 避免内存泄漏
- 确保每个
new都有对应的delete - 确保每个
new[]都有对应的delete[] - 使用RAII(资源获取即初始化)原则管理资源
3. 避免悬空指针
- 释放内存后将指针设置为
nullptr - 使用智能指针自动管理指针的生命周期
4. 避免重复释放
- 释放内存后将指针设置为
nullptr(delete nullptr是安全的) - 使用智能指针自动管理释放
5. 处理内存分配失败
- 使用
new(nothrow)处理分配失败 - 捕获
std::bad_alloc异常
6. 优化内存使用
- 合理估计内存需求
- 避免频繁的内存分配和释放
- 使用内存池技术管理频繁分配的小对象
常见错误和陷阱
1. 内存泄漏
1 2 3 4 5 6 7 8 9 10 11
| void function() { int* p = new int(100); }
void function() { std::unique_ptr<int> p(new int(100)); }
|
2. 悬空指针
1 2 3 4 5 6 7 8 9 10 11 12
| int* p = new int(100); delete p; std::cout << *p << std::endl;
int* p = new int(100); delete p; p = nullptr; if (p != nullptr) { std::cout << *p << std::endl; }
|
3. 重复释放
1 2 3 4 5 6 7 8 9
| int* p = new int(100); delete p; delete p;
std::unique_ptr<int> p(new int(100)); p.reset(); p.reset();
|
4. 数组和单个对象释放混淆
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| int* p = new int[5]; delete p;
int* p = new int; delete[] p;
int* pArray = new int[5]; delete[] pArray;
int* pObj = new int; delete pObj;
|
5. 浅拷贝
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
| class ShallowCopy { private: int* data;
public: ShallowCopy(int value) { data = new int(value); } ~ShallowCopy() { delete data; } };
class DeepCopy { private: int* data;
public: DeepCopy(int value) { data = new int(value); } DeepCopy(const DeepCopy& other) { data = new int(*other.data); } DeepCopy& operator=(const DeepCopy& other) { if (this != &other) { delete data; data = new int(*other.data); } return *this; } ~DeepCopy() { delete data; } };
|
6. 循环引用
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
| class A { private: std::shared_ptr<B> bPtr;
public: void setB(const std::shared_ptr<B>& b) { bPtr = b; } };
class B { private: std::shared_ptr<A> aPtr;
public: void setA(const std::shared_ptr<A>& a) { aPtr = a; } };
class A { private: std::shared_ptr<B> bPtr;
public: void setB(const std::shared_ptr<B>& b) { bPtr = b; } };
class B { private: std::weak_ptr<A> aPtr;
public: void setA(const std::shared_ptr<A>& a) { aPtr = a; } };
|
小结
本章介绍了C++中类和动态内存分配的相关内容,包括:
- 动态内存分配概述:静态内存、栈内存、堆内存的区别
- new 和 delete 运算符:分配和释放单个对象、数组
- 类中的动态内存管理:包含指针成员的类、拷贝构造函数、赋值运算符
- 移动语义:移动构造函数和移动赋值运算符
- 智能指针:unique_ptr、shared_ptr、weak_ptr
- 异常安全:异常安全的概念和实现
- 动态内存管理的最佳实践:使用智能指针、避免内存泄漏、悬空指针等
- 常见错误和陷阱:内存泄漏、悬空指针、重复释放、数组和单个对象释放混淆、浅拷贝、循环引用
动态内存管理是C++编程中的重要部分,掌握好动态内存管理对于编写高效、可靠的程序至关重要。通过合理使用智能指针、遵循RAII原则、注意异常安全等最佳实践,可以避免许多常见的内存管理错误,提高程序的质量和可靠性。
在后续章节中,我们将学习类的继承、多态、模板等更高级的C++特性,这些特性将与动态内存管理结合使用,帮助我们构建更复杂、更强大的程序。