C++ new & new operator

本文详细介绍了C++中的new操作符及其工作原理,包括调用operator new分配内存,然后通过placement new在指定位置调用构造函数。还探讨了operator new与operator delete的配对使用,以及如何通过重载它们来实现内存池。同时,文章提到了placement new在内存池中的应用,以及STL如何利用placement new和内存池进行对象管理,包括插入和释放操作的细节,强调了深拷贝和浅拷贝的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C++ new & new operator

基于sgi-stl


new operator

new operator 是C++中在堆上构建对象的操作符,其语法如下:

ClassT* ptr = new ClassT;

其调用过程如下:

  • 调用operator new, C++中提供了默认的operator new,调用malloc
  • 在operator new返回的地址上调用构造函数, 这一步通过placement new实现
  • 将operator new 申请的对象转换为ClassT* 并返回

operator new & operator delete

  • 函数说明
    • 成对出现的, new负责申请,delete负责释放
    • 为new operator 分配(释放)内存
  • 函数原型
static void* operator new( size_t count )

  • 应用 :如通过重载operator new实现内存池: 预先分配一个固定大小的二维数组,调用operator new时,从数组中找一个没有使用的节点出来,使用完成之后再放回去。

构造对象

申请内存
内存池管理
ptr不为nullptr
ptr 为 nullptr
Test* ptr = new Test
operator new
在 operator new 函数中从内存池中抠取内存
返回指针ptr
在ptr处调用构造函数
throw std::bad_alloc

释放对象

调用析构
释放内存
内存池管理
delete ptr
调用Test的析构函数
调用operator delete
在 operator delete 函数中将内存返还给mempool

  • 代码示例
char m_buffer[4] = {0};

class Test{
public:
    Test(int var) : m_testValue(var) {
        std::cout << "Test constructor with initialize list called" << std::endl;
    }
    Test() { std::cout << "Test constructor called" << std::endl; }
    ~Test() { std::cout << "Test destructor called" << std::endl; }
    
    static void* operator new(size_t count) { 
        std::cout << "operator new called" << std::endl;
        return m_buffer;
    }

    static void operator delete(void* ptr) {
        std::cout << "operator delete called" << std::endl;
    }
public:
    int m_testValue;
};


int main(int argc, char** argv){
    Test* ptr = new Test;
    std::cout << "ptr value = " << ptr->m_testValue << std::endl;   // 0

    Test* ptr2 = new Test(123);
    std::cout << "ptr value = " << ptr->m_testValue << std::endl;  // 12

    delete ptr;
    delete ptr2;
}

上述代码中,ptr ptr2实际是一个对象, 都是指向m_buffer
如果在实现过程中, 将m_buffer调整为一系列内存块数组,在每次调用operator new的时候从内存块中取不同的节点,那么就可以实现一个简单的内存池。

但是STL中没有采用这种实现方式,因为这种实现需要各个类重写operator new & operator delete

  • operator new[] & operator delete[] 略,operator new & delete 的数组版

在指定位置调用构造函数

  • placement new
    • 函数说明
      • 这个函数是operator new的另外一个重载版本
      • 作用是返回一个内存,便于在该内存出调用构造函数(placement的意思就是从这里来的,将构造函数 放置 在指定内存位置)
      • 该函数并不执行内存分配相关事项,仅仅是返回一个指针,具体的内存管理有其他业务实现
      • 该函数的调用缺省了第一个参数(见后面代码示例)
    • 函数原型
void* operator new(size_t count, void* ptr)
  • 应用
    通过***placement new***实现内存池比通过***operator new***要方便很多:
    • 由于placement new 只是在指定位置调用构造函数,故不需要重写(@override)
    • placement new 将内存的申请和构造函数的调用分开了,具有较高的灵活性。

通过placement new实现内存池的流程如下:

申请
申请内存
ptr 不为 nullptr
ptr 为 nullptr
释放
返还
返还内存
申请内存
释放内存
开始 申请对象
内存池调度模块
内存池
获取到内存ptr
对象管理模块
throw std::bad_alloc
通过placement new调用构造
开始 释放对象
调用析构函数
获取size_t大小内存块
将size_t大小内存返还到内存池

  • 代码示例
char* m_buffer;
void init() {
    m_buffer = (char*)malloc(4);
    memset(m_buffer, 0, 4);
}
/
class Test{
public:
    int m_testValue;
    Test(int var) : m_testValue(var) {
        std::cout << "Test constructor with initialize list called" << std::endl;
    }
    Test() {
        std::cout << "Test constructor called" << std::endl; 
    }
   ~Test() { 
       std::cout << "Test destructor called" << std::endl; 
   }
       
    // placement new
    static void* operator new(size_t count, void* ptr) {
        std::cout << "placement new called" << std::endl;
        return ptr;
    }
};

/
int main(int argc, char** argv){
    init();
        
    // 调用placement new示例
    Test* ptr = new(m_buffer) Test;
    std::cout << "ptr value = " << ptr->m_testValue << std::endl;
    
    Test* ptr2 = new(m_buffer) Test(12);
    std::cout << "ptr value = " << ptr->m_testValue << std::endl;

    // placement new对象的析构
    ptr2->~Test();
    ptr1->~Test();
}

  • 由于placement new并不直接申请内存,它只是将构造函数 放置 在指定位置,所以通过placement new构造出来的对象不能调用delete, 只能显式调用析构进行销毁
  • STL 中使用了placement new实现内存池的方法:
    • 将内存的申请与对象的构造分为两步, stl_alloc & stl_construct, 其中stl_alloc负责内存管理, stl_construct负责调用构造及析构
    • stl_alloc:
      • 一级构造器, 直接调用malloc & free进行内存管理, 适用于大内存(>128字节的对象的申请)
      • 二级构造器, 维护一个数组,数组中的node为一个链表,如下表所示:
    • stl_construct:
      • 调用构造函数
      • 调用析构函数

二级构造器

8字节 链表16字节 链表128字节 链表
Node11Node21Nodek1
Node12Node22Nodek2

  • 关于STL的一些说明
    • STL 在执行insert 或者 push等函数时,会调用operator = 或者 拷贝构造函数,所以STL的插入操作实际上是一个复制对象的过程
    • STL插入操作的具体过程如下:
      • 调用stl_alloc,申请一段能够容纳当前对象的内存(从一级构造器或者二级构造器)
      • 调用stl_construct,通过placement new调用拷贝构造函数(或operator=,要看具体的实现)
      • 将申请出来的对象插入到容器本身的数据结构中(如list的双向循环链表、map的rb-tree、unorderd_map的hash_table中)
    • STL释放操作过程:
      • 调用stl_construct,通过调用析构释放类中申请的对象
      • 调用stl_alloc,将对象本身的内存释放(一级构造器或者二级构造器)
      • 从容器本身的数据结构中弹出对象
    • STL中的数据安全性:
      • deep copy & shallow copy,关于深拷贝和浅拷贝主要是在拷贝构造函数或者是operator=中实现,在设计中需要注意
      • STL中会保留对象的副本,如果是将指针放入到容器中需要注意不要在外部将指针删除,如:
        std::list<Test*> testLst;
        将Test* 对象放入容器这里并不会调用Test的拷贝构造(原因是类型是Test* )之后,直接delete这个 Test* 对象将会导致testLst中存放了一个野指针。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值