第11章 内存与资源管理

内存管理基础

内存区域

C++程序运行时,内存通常分为以下几个区域,每个区域都有其特定的用途和管理方式:

  1. 代码区(Text Segment):存储程序的可执行机器代码,通常是只读的,以防止程序意外修改自身指令
  2. 只读数据区(RODATA):存储字符串字面量和其他常量,同样是只读的
  3. 全局/静态区(Data Segment):存储初始化的全局变量和静态变量,程序启动时分配,结束时释放
  4. 未初始化数据区(BSS Segment):存储未初始化的全局变量和静态变量,程序启动时会被自动初始化为0
  5. 堆区(Heap):动态分配的内存,由程序员通过new/deletemalloc/free管理,分配和释放的顺序可以任意
  6. 栈区(Stack):存储函数调用时的局部变量、函数参数和返回地址,由编译器自动管理,遵循后进先出(LIFO)原则

内存布局示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
高地址
┌─────────────────────────────────┐
│ 栈区 (Stack) │ ← 向下增长
├─────────────────────────────────┤
│ 堆区 (Heap) │ ← 向上增长
├─────────────────────────────────┤
│ 未初始化数据区 (BSS Segment) │
├─────────────────────────────────┤
│ 初始化数据区 (Data Segment) │
├─────────────────────────────────┤
│ 只读数据区 (RODATA) │
├─────────────────────────────────┤
│ 代码区 (Text Segment) │
└─────────────────────────────────┘
低地址

内存分配方式

C++中有三种基本内存分配方式,每种方式都有其特定的适用场景和性能特点:

  1. 静态分配:编译时由编译器分配,如全局变量、静态变量

    • 优点:分配速度快,无需运行时开销
    • 缺点:生命周期固定,无法在运行时调整大小
    • 适用场景:存储程序运行期间始终需要的数据,如配置参数、全局状态
  2. 栈分配:函数调用时由编译器自动分配,函数返回时自动释放

    • 优点:分配和释放速度极快,仅需修改栈指针
    • 缺点:空间有限,生命周期受限
    • 适用场景:存储函数局部变量和参数,生命周期与函数调用一致
  3. 堆分配:运行时通过内存分配器手动分配,需要手动释放

    • 优点:空间较大,生命周期灵活
    • 缺点:分配和释放速度较慢,需要手动管理
    • 适用场景:存储大小不确定或生命周期与函数调用无关的数据

性能比较

  • 分配速度:栈分配 > 静态分配 > 堆分配
  • 空间大小:堆分配 > 静态分配 > 栈分配
  • 灵活性:堆分配 > 静态分配 > 栈分配

内存分配的底层实现

  • 栈分配:通过修改栈指针(ESP/RSP寄存器)实现,分配时栈指针减少,释放时栈指针增加
  • 堆分配:通过内存分配器(如ptmalloc、tcmalloc、jemalloc)管理,维护空闲内存块链表,分配时查找合适大小的块,释放时将块放回链表
  • 静态分配:在程序加载时由操作系统加载器分配,位于数据段或BSS段

动态内存分配

newdelete运算符

C++使用newdelete运算符进行动态内存分配和释放,它们在C的malloc/free基础上增加了对象的构造和析构功能。

基本用法

1
2
3
4
5
6
7
8
9
10
11
12
// 分配单个对象
int* p = new int;
*p = 42;
std::cout << *p << std::endl;
delete p;

// 分配数组
int* arr = new int[10];
for (int i = 0; i < 10; ++i) {
arr[i] = i;
}
delete[] arr;

初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 分配并初始化单个对象
int* p1 = new int(100);

// 分配并初始化数组
int* arr1 = new int[5]{1, 2, 3, 4, 5};

// 分配并默认初始化(零初始化内置类型)
int* p2 = new int{};

// 分配并值初始化数组
int* arr2 = new int[5]{};

// 分配并列表初始化对象
class Point {
public:
Point(int x, int y) : x(x), y(y) {}
int x, y;
};

Point* p3 = new Point{10, 20};
delete p3;

定位new

定位new(Placement New)允许在指定的内存位置创建对象,而不分配新的内存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <new>

// 预分配内存
char buffer[sizeof(int)];

// 在指定位置构造对象
int* p = new (buffer) int(42);

// 使用对象
std::cout << *p << std::endl;

// 不需要使用delete,因为内存不是从堆分配的
// 但如果对象有析构函数,需要手动调用
// p->~int(); // 对于内置类型,析构函数调用是可选的

定位new的高级应用

  • 内存池:预分配大块内存,然后在其中构造对象
  • 自定义分配器:实现特定的内存分配策略
  • 对象重用:在同一内存位置重复构造不同类型的对象

newdelete的底层实现

new运算符的工作过程:

  1. 调用operator new分配原始内存
  2. 在分配的内存上调用构造函数
  3. 返回指向新对象的指针

delete运算符的工作过程:

  1. 调用对象的析构函数
  2. 调用operator delete释放内存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// new的伪代码实现
T* new_T() {
void* memory = operator new(sizeof(T));
try {
new (memory) T(); // 调用构造函数
return static_cast<T*>(memory);
} catch (...) {
operator delete(memory); // 构造失败时释放内存
throw;
}
}

// delete的伪代码实现
void delete_T(T* ptr) {
if (ptr) {
ptr->~T(); // 调用析构函数
operator delete(ptr); // 释放内存
}
}

重载newdelete运算符

C++允许重载newdelete运算符,以实现自定义的内存分配策略:

  1. 全局重载:影响整个程序的内存分配
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 全局重载operator new
void* operator new(size_t size) {
std::cout << "Custom new called for size " << size << std::endl;
void* p = std::malloc(size);
if (!p) {
throw std::bad_alloc{};
}
return p;
}

// 全局重载operator delete
void operator delete(void* p) noexcept {
std::cout << "Custom delete called" << std::endl;
std::free(p);
}
  1. 类级重载:仅影响特定类的对象分配
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
class MyClass {
public:
// 类级重载operator new
void* operator new(size_t size) {
std::cout << "MyClass new called for size " << size << std::endl;
void* p = std::malloc(size);
if (!p) {
throw std::bad_alloc{};
}
return p;
}

// 类级重载operator delete
void operator delete(void* p) noexcept {
std::cout << "MyClass delete called" << std::endl;
std::free(p);
}

// 数组版本
void* operator new[](size_t size) {
std::cout << "MyClass new[] called for size " << size << std::endl;
void* p = std::malloc(size);
if (!p) {
throw std::bad_alloc{};
}
return p;
}

void operator delete[](void* p) noexcept {
std::cout << "MyClass delete[] called" << std::endl;
std::free(p);
}
};
  1. 放置版本重载:自定义定位new的行为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 自定义放置new
void* operator new(size_t size, const char* file, int line) {
std::cout << "Placement new called from " << file << ":" << line << std::endl;
return operator new(size);
}

// 对应的delete(仅在构造函数抛出异常时调用)
void operator delete(void* p, const char* file, int line) noexcept {
std::cout << "Placement delete called from " << file << ":" << line << std::endl;
operator delete(p);
}

// 使用示例
#define DEBUG_NEW new(__FILE__, __LINE__)
MyClass* p = DEBUG_NEW MyClass();
delete p;

内存分配策略

  1. 内存池:预分配大块内存,然后将其分割成小块进行分配,减少内存碎片
  2. 对象池:为特定类型的对象预分配内存,避免频繁的构造和析构
  3. 区域分配器:为特定生命周期的对象分配内存,一次性释放整个区域
  4. 缓存分配器:缓存最近释放的内存块,以加速后续的分配请求

内存池示例

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
class MemoryPool {
public:
MemoryPool(size_t blockSize, size_t blockCount)
: blockSize(blockSize), blockCount(blockCount) {
// 分配大块内存
pool = static_cast<char*>(std::malloc(blockSize * blockCount));
if (!pool) {
throw std::bad_alloc{};
}

// 初始化自由块链表
for (size_t i = 0; i < blockCount; ++i) {
char* block = pool + i * blockSize;
*reinterpret_cast<char**>(block) = freeList;
freeList = block;
}
}

~MemoryPool() {
std::free(pool);
}

// 分配内存
void* allocate() {
if (!freeList) {
throw std::bad_alloc{};
}

void* block = freeList;
freeList = *reinterpret_cast<char**>(freeList);
return block;
}

// 释放内存
void deallocate(void* ptr) {
if (!ptr) return;

// 将块放回自由链表
*reinterpret_cast<char**>(ptr) = freeList;
freeList = static_cast<char*>(ptr);
}

private:
size_t blockSize;
size_t blockCount;
char* pool;
char* freeList;
};

// 使用内存池
MemoryPool pool(sizeof(int), 100);
int* p = static_cast<int*>(pool.allocate());
*p = 42;
pool.deallocate(p);

智能指针

C++11引入了智能指针,用于自动管理动态内存,避免内存泄漏。智能指针通过RAII(资源获取即初始化)机制,在对象生命周期结束时自动释放所管理的资源。

std::unique_ptr

std::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
#include <memory>

// 创建unique_ptr
std::unique_ptr<int> p1(new int(42));

// 使用make_unique(推荐,更安全,避免异常安全问题)
auto p2 = std::make_unique<int>(100);

// 转移所有权
std::unique_ptr<int> p3 = std::move(p1); // p1现在为空

// 访问对象
std::cout << *p2 << std::endl;
std::cout << *p3 << std::endl;

// 检查是否为空
if (p1) {
std::cout << *p1 << std::endl;
} else {
std::cout << "p1 is null" << std::endl;
}

// 重置智能指针
p2.reset(); // 释放p2管理的内存

// 替换所管理的对象
p3.reset(new int(200)); // 释放旧对象,管理新对象

// 获取原始指针
int* rawPtr = p3.get();

// unique_ptr会自动释放内存,不需要手动调用delete

std::unique_ptr的性能优势

  • 大小与原始指针相同(通常为8字节)
  • 无引用计数开销
  • 移动操作开销极小
  • 适用于大多数需要独占所有权的场景

std::unique_ptr与数组

1
2
3
4
5
6
7
8
9
// 管理数组
std::unique_ptr<int[]> arr(new int[10]);
for (int i = 0; i < 10; ++i) {
arr[i] = i;
}
// 自动使用delete[]释放

// 使用make_unique创建数组(C++14+)
auto arr2 = std::make_unique<int[]>(10);

std::shared_ptr

std::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
#include <memory>

// 创建shared_ptr
std::shared_ptr<int> p1(new int(42));

// 使用make_shared(推荐,更高效,只分配一次内存)
auto p2 = std::make_shared<int>(100);

// 共享所有权
std::shared_ptr<int> p3 = p1;

// 访问对象
std::cout << *p1 << std::endl;
std::cout << *p2 << std::endl;
std::cout << *p3 << std::endl;

// 查看引用计数
std::cout << "p1 use_count: " << p1.use_count() << std::endl; // 输出2

// 重置shared_ptr
p1.reset(); // 引用计数减1,现在为1
std::cout << "p3 use_count after p1.reset(): " << p3.use_count() << std::endl; // 输出1

// shared_ptr会在最后一个引用被销毁时自动释放内存

std::shared_ptr的内部实现

  • 控制块:存储引用计数、弱引用计数和自定义删除器
  • 内存布局:当使用make_shared时,控制块和对象在同一块内存中分配,减少内存开销

std::shared_ptr的性能考虑

  • 大小为两个指针(通常为16字节)
  • 引用计数操作是原子的,有一定的线程安全开销
  • 避免使用shared_ptr(new T()),优先使用make_shared<T>()

std::weak_ptr

std::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
#include <memory>

class B;

class A {
public:
std::shared_ptr<B> b;
~A() { std::cout << "A destroyed" << std::endl; }
};

class B {
public:
std::weak_ptr<A> a; // 使用weak_ptr避免循环引用
~B() { std::cout << "B destroyed" << std::endl; }
};

int main() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();

a->b = b;
b->a = a;

// 检查引用计数
std::cout << "a use_count: " << a.use_count() << std::endl; // 输出1
std::cout << "b use_count: " << b.use_count() << std::endl; // 输出2

// 从weak_ptr获取shared_ptr
if (auto locked = b->a.lock()) {
std::cout << "A still exists" << std::endl;
} else {
std::cout << "A has been destroyed" << std::endl;
}

// 不会导致内存泄漏,两个对象都会被正确销毁
return 0;
}

std::weak_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
#include <memory>
#include <fstream>

// 自定义删除器(函数对象)
struct FileDeleter {
void operator()(std::FILE* fp) {
if (fp) {
std::fclose(fp);
std::cout << "File closed" << std::endl;
}
}
};

// 使用自定义删除器
std::unique_ptr<std::FILE, FileDeleter> filePtr(std::fopen("test.txt", "w"));

// 使用lambda作为删除器
auto deleter = [](int* p) {
std::cout << "Custom deleter called" << std::endl;
delete p;
};

std::unique_ptr<int, decltype(deleter)> p(new int(42), deleter);

// shared_ptr使用自定义删除器
std::shared_ptr<int> sp(new int(100), [](int* p) {
std::cout << "Shared deleter called" << std::endl;
delete p;
});

// 管理数组(使用lambda删除器)
std::shared_ptr<int> arrSp(new int[10], [](int* p) {
delete[] p;
});

自定义删除器的应用场景

  • 管理非堆内存资源(如文件句柄、网络连接、数据库连接)
  • 实现特殊的资源释放逻辑(如解锁互斥锁、通知其他线程)
  • 与第三方库集成,使用库特定的释放函数

智能指针的实现原理

std::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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
template<typename T, typename Deleter = std::default_delete<T>>
class UniquePtr {
public:
// 构造函数
explicit UniquePtr(T* ptr = nullptr) : ptr_(ptr) {}

// 析构函数
~UniquePtr() {
if (ptr_) {
deleter_(ptr_);
}
}

// 禁止拷贝
UniquePtr(const UniquePtr&) = delete;
UniquePtr& operator=(const UniquePtr&) = delete;

// 允许移动
UniquePtr(UniquePtr&& other) noexcept : ptr_(other.ptr_) {
other.ptr_ = nullptr;
}

UniquePtr& operator=(UniquePtr&& other) noexcept {
if (this != &other) {
if (ptr_) {
deleter_(ptr_);
}
ptr_ = other.ptr_;
other.ptr_ = nullptr;
}
return *this;
}

// 操作符重载
T& operator*() const { return *ptr_; }
T* operator->() const { return ptr_; }

// 成员函数
T* get() const { return ptr_; }
void reset(T* ptr = nullptr) {
if (ptr_) {
deleter_(ptr_);
}
ptr_ = ptr;
}
T* release() {
T* tmp = ptr_;
ptr_ = nullptr;
return tmp;
}

// 显式转换为bool
explicit operator bool() const { return ptr_ != nullptr; }

private:
T* ptr_;
Deleter deleter_;
};

std::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
60
61
62
63
64
65
66
67
68
69
70
71
72
template<typename T>
class SharedPtr {
public:
// 构造函数
explicit SharedPtr(T* ptr = nullptr) : ptr_(ptr) {
if (ptr_) {
controlBlock_ = new ControlBlock(ptr_);
}
}

// 拷贝构造函数
SharedPtr(const SharedPtr& other) : ptr_(other.ptr_), controlBlock_(other.controlBlock_) {
if (controlBlock_) {
controlBlock_->increment();
}
}

// 析构函数
~SharedPtr() {
if (controlBlock_) {
if (controlBlock_->decrement() == 0) {
delete controlBlock_;
}
}
}

// 拷贝赋值运算符
SharedPtr& operator=(const SharedPtr& other) {
if (this != &other) {
// 释放当前资源
if (controlBlock_) {
if (controlBlock_->decrement() == 0) {
delete controlBlock_;
}
}

// 共享新资源
ptr_ = other.ptr_;
controlBlock_ = other.controlBlock_;
if (controlBlock_) {
controlBlock_->increment();
}
}
return *this;
}

// 操作符重载
T& operator*() const { return *ptr_; }
T* operator->() const { return ptr_; }

// 成员函数
T* get() const { return ptr_; }
long use_count() const {
return controlBlock_ ? controlBlock_->count() : 0;
}

private:
struct ControlBlock {
ControlBlock(T* ptr) : ptr(ptr), refCount(1) {}
~ControlBlock() { delete ptr; }

void increment() { refCount++; }
long decrement() { return --refCount; }
long count() const { return refCount; }

T* ptr;
long refCount;
};

T* ptr_;
ControlBlock* controlBlock_;
};

智能指针与多线程

std::shared_ptr的线程安全性

  • 引用计数的操作是线程安全的(原子操作)
  • 多个线程可以同时读取和修改同一个shared_ptr的引用计数
  • 但对shared_ptr所管理对象的访问不是线程安全的,需要额外的同步措施

std::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
#include <memory>
#include <thread>
#include <mutex>

std::shared_ptr<int> sharedData = std::make_shared<int>(42);
std::mutex dataMutex;

void threadFunction() {
// 安全读取sharedData
{
std::lock_guard<std::mutex> lock(dataMutex);
std::cout << "Thread read: " << *sharedData << std::endl;
}

// 安全修改sharedData
{
std::lock_guard<std::mutex> lock(dataMutex);
*sharedData = 100;
std::cout << "Thread modified: " << *sharedData << std::endl;
}
}

int main() {
std::thread t1(threadFunction);
std::thread t2(threadFunction);

t1.join();
t2.join();

return 0;
}

RAII

RAII的概念

RAII(Resource Acquisition Is Initialization)是一种C++编程技术,用于资源管理。它的核心思想是:

  1. 资源获取时初始化:在构造函数中获取资源
  2. 资源在对象生命周期内保持有效:对象存在时,资源有效
  3. 资源在对象销毁时释放:在析构函数中释放资源

RAII的应用

RAII常用于管理以下资源:

  1. 内存:使用智能指针
  2. 文件句柄:使用RAII包装器
  3. 网络连接:使用RAII包装器
  4. 互斥锁:使用std::lock_guard

自定义RAII类

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
class FileHandler {
public:
FileHandler(const char* filename, const char* mode) {
file = fopen(filename, mode);
if (!file) {
throw std::runtime_error("Failed to open file");
}
}

~FileHandler() {
if (file) {
fclose(file);
}
}

// 禁止拷贝和移动
FileHandler(const FileHandler&) = delete;
FileHandler& operator=(const FileHandler&) = delete;

// 提供文件操作接口
FILE* get() const {
return file;
}

void write(const char* data) {
if (file) {
fprintf(file, "%s", data);
}
}

private:
FILE* file;
};

// 使用示例
void useFile() {
FileHandler file("test.txt", "w");
file.write("Hello, RAII!");
// 文件会在file对象销毁时自动关闭
}

资源管理工具

标准库中的RAII工具

std::lock_guard

std::lock_guard是一个互斥锁的RAII包装器,在构造时锁定互斥锁,在析构时解锁。

1
2
3
4
5
6
7
8
9
#include <mutex>

std::mutex mtx;

void criticalSection() {
std::lock_guard<std::mutex> lock(mtx);
// 临界区代码
// 锁会在lock对象销毁时自动释放
}

std::unique_lock

std::unique_lock是一个更灵活的互斥锁RAII包装器,支持延迟锁定、超时锁定等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <mutex>
#include <chrono>

std::mutex mtx;

void tryLock() {
std::unique_lock<std::mutex> lock(mtx, std::defer_lock);

// 尝试锁定
if (lock.try_lock()) {
// 锁定成功
}

// 尝试超时锁定
if (lock.try_lock_for(std::chrono::seconds(1))) {
// 锁定成功
}
}

std::scoped_lock

C++17引入的std::scoped_lock可以同时锁定多个互斥锁,避免死锁。

1
2
3
4
5
6
7
8
#include <mutex>

std::mutex mtx1, mtx2;

void lockMultiple() {
std::scoped_lock lock(mtx1, mtx2);
// 同时锁定了mtx1和mtx2
}

内存管理最佳实践

1. 优先使用智能指针

尽量使用std::unique_ptrstd::shared_ptr代替原始指针,避免内存泄漏。

2. 使用std::make_uniquestd::make_shared

使用std::make_uniquestd::make_shared创建智能指针,避免异常安全问题。

3. 避免循环引用

使用std::weak_ptr解决std::shared_ptr的循环引用问题。

4. 合理使用RAII

为所有资源创建RAII包装器,确保资源的正确释放。

5. 避免内存泄漏

  • 总是配对使用newdelete
  • 优先使用智能指针
  • 使用RAII管理资源

6. 避免悬空指针

  • 释放内存后将指针置为nullptr
  • 使用智能指针自动管理指针生命周期

7. 避免野指针

  • 总是初始化指针
  • 不使用已释放的指针

内存优化

内存分配策略

  1. 减少动态内存分配:优先使用栈分配和静态分配
  2. 批量分配:使用std::vector等容器,避免频繁的小内存分配
  3. 内存池:对于频繁分配和释放的小对象,使用内存池
  4. 多态内存资源(C++17+):使用std::pmr命名空间中的内存资源管理工具

内存对齐

内存对齐可以提高内存访问速度,C++11引入了alignasalignof关键字来控制内存对齐。

C++20新特性:std::span

std::span是C++20引入的一个非拥有式视图,用于表示连续内存区域的序列,避免不必要的内存拷贝:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <span>
#include <vector>

// 使用span处理数组
void processArray(std::span<int> data) {
for (auto& element : data) {
element *= 2;
}
}

int main() {
// 处理数组
int arr[] = {1, 2, 3, 4, 5};
processArray(arr);

// 处理vector
std::vector<int> vec = {6, 7, 8, 9, 10};
processArray(vec);

// 处理vector的一部分
processArray(std::span(vec).subspan(1, 3));

return 0;
}

C++17新特性:多态内存资源

C++17引入了std::pmr命名空间,提供了多态内存资源管理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <memory_resource>
#include <vector>

int main() {
// 使用单调缓冲区资源(内存池)
char buffer[1024];
std::pmr::monotonic_buffer_resource pool(buffer, sizeof(buffer));

// 使用该内存池分配内存
std::pmr::vector<int> vec(&pool);
for (int i = 0; i < 100; ++i) {
vec.push_back(i);
}

// 内存从buffer分配,不需要额外的动态内存分配

return 0;
}
1
2
3
4
5
6
7
8
9
10
11
// 指定对齐方式
alignas(16) int x;

// 检查对齐要求
std::cout << "alignof(int): " << alignof(int) << std::endl;

// 对齐的结构体
struct alignas(32) MyStruct {
int a;
double b;
};

内存使用分析

使用工具分析内存使用情况,找出内存泄漏和内存使用效率低下的地方。

  1. Valgrind:Linux下的内存分析工具
  2. AddressSanitizer:Google开发的内存错误检测工具
  3. Visual Studio Memory Profiler:Windows下的内存分析工具

示例:内存管理的综合应用

自定义智能指针

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
template<typename T>
class SmartPtr {
public:
explicit SmartPtr(T* ptr = nullptr) : ptr(ptr) {}

~SmartPtr() {
delete ptr;
}

// 禁止拷贝
SmartPtr(const SmartPtr&) = delete;
SmartPtr& operator=(const SmartPtr&) = delete;

// 允许移动
SmartPtr(SmartPtr&& other) noexcept : ptr(other.ptr) {
other.ptr = nullptr;
}

SmartPtr& operator=(SmartPtr&& 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;
}

// 检查是否为空
explicit operator bool() const {
return ptr != nullptr;
}

private:
T* ptr;
};

// 使用示例
void useSmartPtr() {
SmartPtr<int> p(new int(42));
std::cout << *p << std::endl;

SmartPtr<int> p2 = std::move(p);
std::cout << *p2 << std::endl;

// 不需要手动释放内存
}

资源管理类

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
class DatabaseConnection {
public:
DatabaseConnection(const std::string& connectionString) {
// 建立数据库连接
std::cout << "Connecting to database: " << connectionString << std::endl;
// 模拟连接过程
connected = true;
}

~DatabaseConnection() {
// 关闭数据库连接
if (connected) {
std::cout << "Disconnecting from database" << std::endl;
connected = false;
}
}

// 禁止拷贝
DatabaseConnection(const DatabaseConnection&) = delete;
DatabaseConnection& operator=(const DatabaseConnection&) = delete;

// 允许移动
DatabaseConnection(DatabaseConnection&& other) noexcept : connected(other.connected) {
other.connected = false;
}

DatabaseConnection& operator=(DatabaseConnection&& other) noexcept {
if (this != &other) {
// 关闭当前连接
if (connected) {
std::cout << "Disconnecting from database" << std::endl;
}
// 移动连接
connected = other.connected;
other.connected = false;
}
return *this;
}

// 数据库操作
void executeQuery(const std::string& query) {
if (connected) {
std::cout << "Executing query: " << query << std::endl;
} else {
throw std::runtime_error("Not connected to database");
}
}

private:
bool connected;
};

// 使用示例
void useDatabase() {
try {
DatabaseConnection db("server=localhost;database=test");
db.executeQuery("SELECT * FROM users");

// 连接会在db对象销毁时自动关闭
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
}

总结

内存与资源管理是C++编程中的重要主题,直接关系到程序的性能和稳定性。通过合理使用动态内存分配、智能指针和RAII等技术,可以有效地管理内存和资源,避免内存泄漏、悬空指针等问题。

在现代C++中,应该优先使用智能指针和RAII来管理内存和资源,减少手动内存管理的错误。同时,合理的内存分配策略和内存优化技术可以提高程序的性能和内存使用效率。

通过本章的学习,读者应该掌握C++中的内存管理和资源管理技术,能够编写更加安全、高效的C++程序。