内存管理简介

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

C++教程视频

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

理解这些内存管理方法对于编写高效、安全的 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 用于释放之前由 malloccallocrealloc 分配的内存。其原型如下:

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++ 提供了更高层次的内存管理操作符:newdelete,它们不仅分配和释放内存,还调用构造函数和析构函数,提供类型安全。

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;

区别于 mallocfree

  • 类型安全new 返回正确类型的指针,免去了强制类型转换。
  • 构造/析构newdelete 自动调用构造函数和析构函数。
  • 异常处理:在分配失败时,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/freenew/delete 的区别,有助于在编写 C++ 程序时正确选择内存管理方法。

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

示例对比

使用 mallocfree

#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

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

高级内存管理

使用 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

动态数组管理

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

封装动态数组

#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 管理固定大小的内存块,避免频繁调用 mallocfree
  • 使用“定位 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 将所有权转移到 ptr2ptr1 变为 nullptr
  • 使用 release() 释放 ptr2 的所有权,获取裸指针后需要手动 delete
  • 使用 reset() 重新指向一个新的 Test 对象,自动释放之前的资源。

总结

源码连接

https://gitee.com/secondtonone1/boostasio-learn/tree/master/base/day22-memory

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

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

results matching ""

    No results matching ""