【C++内存管理】重载 new

1. 为什么要重载 new

1.1 监测内存创建销毁,统计和监控泄漏

在C++中,内存管理是开发者的一项重要责任,也是容易出错的地方。开发者可能会遗忘释放已分配的内存,导致内存泄漏。重载new和delete可以帮助开发者更好地追踪和管理内存分配。通过在重载的new和delete操作符中插入日志或者调试语句,开发者可以监测和记录所有内存分配和释放的情况,从而检测内存泄漏。

例如,以下的代码展示了如何重载new和delete操作符来监测和追踪内存分配:

void* operator new(size_t size) {
    
    
    void* p = malloc(size);
    std::cout << "Allocated " << size << " bytes at address " << p << std::endl;
    return p;
}

void operator delete(void* p) {
    
    
    std::cout << "Deallocated memory at address " << p << std::endl;
    free(p);
}

1.2 内存对齐的处理

在一些硬件平台和操作系统上,为了实现最优性能,数据需要按照某种特定的边界对齐。如果没有对齐,可能会导致性能下降,甚至运行错误。通过重载new和delete,我们可以为特定的类实现定制的内存对齐方式。

下面的代码演示了如何重载new和delete操作符来实现内存对齐:

class Aligned {
    
    
public:
    static void* operator new(std::size_t size) {
    
    
        void* p = std::aligned_alloc(alignof(Aligned), size);
        if (!p) {
    
    
            throw std::bad_alloc();
        }
        return p;
    }

    static void operator delete(void* p) {
    
    
        std::free(p);
    }
};

1.3 特定应用:多进程内存共享

在某些情况下,多个进程可能需要访问同一块内存区域。在这种情况下,可以通过重载new和delete操作符,实现在共享内存区域中分配和释放对象。

例如,以下的代码展示了如何通过重载new和delete来在共享内存中分配和释放对象:

// 假设SharedMemoryManager是一个用于管理共享内存的类
class SharedMemoryManager {
    
    
public:
    void* allocate(size_t size);
    void deallocate(void* p);
};

class SharedMemoryObject {
    
    
public:
    void* operator new(size_t size) {
    
    
        return SharedMemoryManager::allocate(size);
    }

    void operator delete(void* p) {
    
    
        SharedMemoryManager::deallocate(p);
    }
};

在以上的例子中,SharedMemoryObject类的对象将会被分配在共享内存中,从而可以被多个进程访问。

2. 重载全局的 new 和 delete

全局的new和delete操作符可被重载以满足特定的需求,比如定制内存管理策略,或者为内存分配和释放添加自定义行为。要注意,这些全局重载将影响到整个程序的范围,包括标准库的容器等,所以在实践中应谨慎使用。

2.1 全局new和delete操作符重载基础

全局的new和delete操作符可以通过以下方式进行重载:

void* operator new(size_t size) {
    
    
    // ... 实现代码
}

void operator delete(void* p) {
    
    
    // ... 实现代码
}

operator new需要返回一个足够大,可以容纳请求内存大小的指针。如果内存分配失败,需要抛出std::bad_alloc异常。operator delete需要释放传入的指针指向的内存。

2.2 在全局new和delete中添加定制行为

下面的代码将在全局的new和delete操作符中添加一些定制的行为。在分配和释放内存时,我们会打印一些信息到控制台,以便于跟踪内存的使用情况:

#include <cstdlib>
#include <iostream>

void* operator new(size_t size) {
    
    
    void* p = std::malloc(size);
    if (!p) {
    
    
        throw std::bad_alloc();
    }

    std::cout << "Allocated " << size << " bytes at address " << p << std::endl;
    return p;
}

void operator delete(void* p) {
    
    
    std::cout << "Deallocated memory at address " << p << std::endl;
    std::free(p);
}

以上代码演示了如何在全局的new和delete操作符中添加自定义的行为。这种方式在实际开发中可以帮助我们更好地理解和跟踪内存的使用情况。不过请注意,添加过多的日志可能会对性能产生影响,所以在生产环境中通常不会添加过多的日志信息。

3. 重载类的操作符 new 和 delete

对类的new和delete操作符进行重载允许我们为该类的对象提供定制的内存管理策略。这对于需要进行特殊内存管理的类来说特别有用,例如需要在共享内存中创建的对象,或者需要进行特殊对齐的对象。

3.1 类new和delete操作符重载基础

对类的new和delete操作符进行重载的基本形式如下:

class MyClass {
    
    
public:
    static void* operator new(std::size_t size);
    static void operator delete(void* p);
};

operator new需要返回一个足够大,可以容纳请求内存大小的指针。如果内存分配失败,需要抛出std::bad_alloc异常。operator delete需要释放传入的指针指向的内存。

3.2 对齐的内存分配

假设我们有一个需要8字节对齐的类,我们可以通过重载new和delete操作符来满足这个要求:

#include <cstdlib>
#include <new>

class Aligned {
    
    
public:
    static void* operator new(std::size_t size) {
    
    
        void* p = std::aligned_alloc(8, size);
        if (!p) {
    
    
            throw std::bad_alloc();
        }
        return p;
    }

    static void operator delete(void* p) {
    
    
        std::free(p);
    }
};

在这个例子中,我们使用了std::aligned_alloc函数来进行对齐的内存分配。如果分配失败,我们抛出std::bad_alloc异常。在operator delete中,我们简单地调用std::free来释放内存。

3.3 共享内存的分配

假设我们有一个需要在共享内存中创建的对象,我们可以通过重载new和delete操作符来实现:

// 假设SharedMemoryManager是一个用于管理共享内存的类
class SharedMemoryManager {
    
    
public:
    static void* allocate(std::size_t size);
    static void deallocate(void* p);
};

class SharedMemoryObject {
    
    
public:
    static void* operator new(std::size_t size) {
    
    
        return SharedMemoryManager::allocate(size);
    }

    static void operator delete(void* p) {
    
    
        SharedMemoryManager::deallocate(p);
    }
};

在这个例子中,SharedMemoryObject类的对象将会在共享内存中创建和销毁。这允许我们在多个进程间共享这些对象。

4. 放置 placement new 和 delete、new 的空间指向已有的地址中

放置new (placement new) 是一个特殊版本的new操作符,它允许程序员将对象创建在已经分配的内存上。换句话说,它允许我们"放置"一个新的对象在我们指定的、已经存在的内存位置上。

4.1 使用placement new

在普通的new操作中,首先会申请一块足够的内存,然后在这块内存上构造对象。但是在placement new中,内存必须已经存在,它只负责在指定的内存上构造对象。以下是一个使用placement new的例子:

#include <new> // 需要包含这个头文件来使用placement new

char buffer[1024]; // 预分配的内存

int* p = new (buffer) int(123); // 在buffer上放置一个int对象

4.2 placement new对象的销毁

由于placement new仅仅在已经分配的内存上创建对象,而不会分配内存,所以当不再需要这个对象时,我们需要手动调用该对象的析构函数来销毁对象:

p->~int(); // 手动调用析构函数

需要注意的是,这里只销毁了对象,但并没有释放内存。内存的释放需要根据实际的情况来处理。例如,如果这块内存是在栈上分配的,那么当退出作用域时会自动释放;如果是在堆上分配的,那么可能需要手动释放。

4.3 placement new的应用

placement new的一个主要应用是当我们需要在特定的位置创建对象时,比如在已分配的堆内存上,或者在栈内存上,甚至在硬件指定的特定内存地址上。

此外,placement new也常用于实现自定义的内存池,内存池可以减少动态分配和释放内存带来的开销。我们可以预先分配一大块内存,然后通过placement new在这块内存上创建对象,从而提高内存使用的效率。

猜你喜欢

转载自blog.csdn.net/weixin_52665939/article/details/131620052
今日推荐