文章目录

C++ placement new在内存池管理中的定位构造

发布于 2026-05-05 23:23:10 · 浏览 14 次 · 评论 0 条

C++ placement new在内存池管理中的定位构造

在编写对性能要求极高的服务器程序或游戏引擎时,频繁的内存申请与释放会导致内存碎片化并增加 CPU 开销。为了解决这个问题,我们通常使用“内存池”技术预先申请一大块内存,后续直接从这块内存中分配。C++ 提供的 placement new 语法,正是实现这一过程的核心工具,它允许我们在已有的内存地址上直接构造对象,而无需重新分配。

本文将手把手教你如何利用 placement new 在内存池中实现对象的定位构造与销毁。


1. 理解 Placement New 的核心机制

普通的 new 操作符做了两件事:向操作系统申请内存,然后在该内存上调用对象的构造函数。而 placement new 将这两件事解耦,它跳过申请内存的步骤,直接在指定的内存地址上调用构造函数。

理解以下区别是进行内存池开发的前提:

特性 普通 new Placement new
内存分配 自动从堆分配 使用已分配的内存地址
构造函数 调用 调用
使用场景 通用对象创建 内存池、嵌入式系统、共享内存
语法格式 new ClassName new (address) ClassName

2. 准备工作:引入必要的头文件

使用 placement new 不需要安装任何第三方库,但必须包含特定的 C++ 标准头文件。

打开你的 C++ 源代码文件。

添加以下代码到文件顶部:

#include <iostream>
#include <new>      // 必须包含,用于 placement new
#include <cstdlib>  // 用于 malloc 和 free

3. 设计内存池的基本结构

为了演示,我们将设计一个最简化的内存块结构。这个结构包含一个数据区,用于存放实际对象。

定义一个简单的类作为我们要管理的对象:

class MyObject {
public:
    int id;
    double value;

    MyObject(int i, double v) : id(i), value(v) {
        std::cout << "对象构造 ID: " << id << std::endl;
    }

    void show() {
        std::cout << "对象 ID: " << id << ", 值: " << value << std::endl;
    }

    ~MyObject() {
        std::cout << "对象析构 ID: " << id << std::endl;
    }
};

4. 执行定位构造:使用 Placement New

这是最关键的一步。假设你已经通过 malloc 或者其他方式从大内存块中切分出了一块闲置内存,现在要把这块内存变成一个 MyObject 对象。

声明一个指向 void 的指针,模拟从内存池获取的原始内存地址:

void* rawMemory = std::malloc(sizeof(MyObject));
std::cout << "获取原始内存地址: " << rawMemory << std::endl;

调用 placement new 语法在 rawMemory 上构造对象:

// 语法:new (指针地址) 类名(构造参数)
MyObject* obj = new (rawMemory) MyObject(101, 3.14);

注意:这里 obj 指针的值通常等于 rawMemory,但类型不同。rawMemory 只是一堆字节,而 obj 是一个合法的对象指针。

调用对象的方法验证构造是否成功:

obj->show();

此时,控制台将输出构造信息,证明对象已经在指定的内存地址上成功创建。


5. 流程图解:内存状态转换

为了更直观地理解内存中发生的变化,请参考下方的状态流转过程。内存从“原始数据”状态,经过 placement new 处理,变成了“活跃对象”,最后经过手动析构又变回了“原始数据”。

stateDiagram-v2 [*] --> RawMemory: malloc/内存池分配 RawMemory --> ActiveObject: placement new\nnew (ptr) Class(args) ActiveObject --> RawMemory: 显式调用析构\nptr->~Class() RawMemory --> [*]: free/归还内存池 note right of RawMemory 内存内容:随机/垃圾值 类型:void* end note note right of ActiveObject 内存内容:已初始化的对象数据 类型:MyObject* end note

6. 执行销毁:显式调用析构函数

使用 placement new 创建的对象有一个极其重要的规则:严禁使用 delete 关键字释放内存

因为内存本身不是由 new 分配的(可能是 malloc 来的,或者属于内存池的一小部分),delete 会尝试把内存归还给系统堆,导致程序崩溃。我们只需要销毁对象(即运行析构函数代码),保持内存原样不动即可。

执行以下代码来销毁对象但保留内存:

// 语法:指针->~类名()
obj->~MyObject(); 

此时,obj 指向的内存中,MyObject 的部分已经被销毁,但这块内存地址依然存在,可以被后续再次利用。

最后,当你彻底不再需要这块原始内存时(例如要关闭整个内存池),才使用对应的方式释放原始内存:

std::free(rawMemory);

7. 封装一个简易内存池模板

为了实际工程应用,我们将上述步骤封装成一个简单的工具类。你可以直接复制以下代码并在本地编译运行。

编写完整的代码示例:

#include <iostream>
#include <new>
#include <cstdlib>

class MyObject {
public:
    int id;
    MyObject(int i) : id(i) { std::cout << "构造: " << id << std::endl; }
    void work() { std::cout << "工作: " << id << std::endl; }
    ~MyObject() { std::cout << "析构: " << id << std::endl; }
};

// 简易内存池节点
template <typename T>
class ObjectPool {
private:
    void* memoryChunk;
    size_t poolSize;

public:
    ObjectPool(size_t count) {
        // 1. 预先分配一大块内存
        poolSize = count;
        memoryChunk = std::malloc(sizeof(T) * count);
        std::cout << "内存池初始化完成,分配大小: " << sizeof(T) * count << " 字节" << std::endl;
    }

    ~ObjectPool() {
        // 4. 最终释放整个内存池
        std::free(memoryChunk);
        std::cout << "内存池释放" << std::endl;
    }

    // 在指定索引的位置构造对象
    T* createAtIndex(size_t index, int id) {
        if (index >= poolSize) return nullptr;

        // 计算目标地址
        void* targetAddr = static_cast<char*>(memoryChunk) + index * sizeof(T);

        // 2. 使用 placement new 定位构造
        return new (targetAddr) T(id);
    }

    // 销毁指定索引的对象
    void destroyAtIndex(size_t index, T* objPtr) {
        if (index >= poolSize || objPtr == nullptr) return;

        // 3. 显式调用析构函数
        objPtr->~T();
        std::cout << "位置 " << index << " 的对象已销毁,内存归还池中" << std::endl;
    }
};

int main() {
    // 创建一个能容纳 3 个对象的内存池
    ObjectPool<MyObject> pool(3);

    // 在位置 0 创建对象
    MyObject* obj1 = pool.createAtIndex(0, 1);
    if (obj1) obj1->work();

    // 在位置 1 创建对象
    MyObject* obj2 = pool.createAtIndex(1, 2);
    if (obj2) obj2->work();

    std::cout << "--- 开始销毁对象 ---" << std::endl;

    // 销毁对象,但不释放内存池
    pool.destroyAtIndex(0, obj1);
    pool.destroyAtIndex(1, obj2);

    // 这里可以再次利用 createAtIndex 在相同的内存位置创建新对象

    return 0;
    // 程序结束时,pool 析构函数调用 std::free 释放整块内存
}

编译并运行上述代码,观察控制台输出的“构造”与“析构”顺序。


8. 关键操作总结

在处理内存池与定位构造时,请严格遵循以下操作顺序,以免造成内存泄漏或程序崩溃:

  1. 申请原始内存(malloc::operator new 或从预分配池中获取指针)。
  2. 使用 new (address) Class(...) 在内存上构造对象。
  3. 使用对象指针进行业务逻辑操作。
  4. 调用 ptr->~Class() 显式销毁对象(仅清理数据,不释放内存)。
  5. 归还指针给内存池或保持不动。
  6. 系统生命周期结束时,调用 freedelete[] 释放整块原始内存。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文