百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分类 > 正文

C++中的new、operator new与placement new

ztj100 2025-03-14 22:38 39 浏览 0 评论

当我们使用了new关键字去创建一个对象时,你知道背后做了哪些事情吗?

A* a = new A;

实际上这样简单的一行语句, 背后做了以下三件事情:

  1. 分配内存,如果类A重载了operator new,那么将调用A::operator new(size_t )来完成,如果没有重载,就调用::operator new(size_t ),即全局new操作符来完成。
  2. 调用构造函数生成类对象;
  3. 返回相应指针。

下面我们通过一个例子来验证这个过程:

#include 
#include 
#include 
using namespace std;

//student class
class Stu
{
public:
    Stu(string name, int age)
    {
        cout << "call Stu class constructor" << endl; 
        name_ = name;
        age_ = age;
    };
public:
    void print() const
    {
        cout << "name = " << name_ << std::endl;
        cout<< "age = " << age_ << std::endl;
    };
    void* operator new(size_t size)
    {
        std::cout << "call operator new" << std::endl;
        return malloc(size);
    }
private:
    string name_;
    int age_;
};
int main()
{
    Stu* stu1 = new Stu("a", 10);
}

在上述代码中,我们重载了Stu类的operator new操作符,用来验证上述对于new关键字的描述。

上述代码的执行结果如下所示:

call operator new
call Stu class constructor

可以看到重载的operator new被调用,类Stu的构造函数也被调用,验证了上述的描述。

要注意到的是new是一个关键字,和sizeof一样,我们不能修改其具体功能。

operator new

从new的调用过程中,我们知道会调用operator new操作符

那么operator new又是什么呢?

C++支持运算符的重载,支持对一些运算符自定义其行为:

operator new

operator new是一个操作符,和+ -操作符一样,作用是分配空间。我们可以重写它们,修改分配空间的方式。

operator new返回值必须是void*。第一个参数必须是size_t

void* operator new (std::size_t size) throw (std::bad_alloc);  
void* operator new (std::size_t size, const std::nothrow_t& nothrow_constant) throw();  

在下面的例子中,我们使用重载了三个operator new方法, 并分别调用。

#include 
#include 
#include 
using namespace std;

//student class
class Stu
{
public:
    Stu(string name, int age)
    {
        cout << "call Stu class constructor" << endl; 
        name_ = name;
        age_ = age;
    };
    ~Stu()
    {
        cout << "call destructor"  << endl;
    }
public:
    void print() const
    {
        cout << "name = " << name_ << std::endl;
        cout<< "age = " << age_ << std::endl;
    };
    void* operator new(size_t size)
    {
        std::cout << "call operator new" << std::endl;
        return malloc(size);
    }
    void* operator new(size_t size, int num)
    {
        std::cout << "call operator new with int" << std::endl;
        return malloc(size);
    } 
    void* operator new(size_t size, char c)
    {
        std::cout << "call operator new with char" << std::endl;
        return malloc(size);
    }      
private:
    string name_;
    int age_;
};
int main()
{
    Stu* stu1 = new Stu("a", 10);
    Stu* stu2 = new(1) Stu("a", 10);
    Stu* stu3 = new('c') Stu("a", 10);
    delete stu1;
    delete stu2;
    delete stu3;
}

执行结果如下:

call operator new
call Stu class constructor
call operator new with int
call Stu class constructor
call operator new with char
call Stu class constructor
call destructor
call destructor
call destructor

可以看到重载的三个operator new被成功调用。

placement new

placement new是operator new的一种重载形式,其作用是可以在指定的内存地址创建对象

placement new返回值必须是void*。第一个参数必须是size_t, 第二个参数是void*

void* operator new (std::size_t size, void* ptr) throw();  

下面的是一个关于placement new的调用例子:

#include 
#include 
#include 
using namespace std;

//student class
class Stu
{
public:
    Stu(string name, int age)
    {
        name_ = name;
        age_ = age;
    };
    ~Stu()
    {
        cout << "call destructor"  << endl;
    }
public:
    void print() const
    {
        cout << "name = " << name_ << std::endl;
        cout<< "age = " << age_ << std::endl;
    };
    void* operator new(size_t size, void* p)
    {
        std::cout << "placement new" << std::endl return p private: string name_ int age_ int main char buff='(char*)malloc(sizeof(Stu));' stu stu='new' buff stustu1 10 stu->print();
    stu->~Stu();
    free(buff);
}

执行结果如下:

placement new
name = stu1
age = 10
call destructor

由于placement new可以在一个指定的位置创建对象,因此在STL中有很广泛的运用, 例子vector容器初始化的时候,会使用allocator申请一定的内存,当使用push_back放入对象时, 就可以使用placement new在申请的位置创建对象。

这里以MyTinySTL中创建对象的函数为例,construct.hopen in new window, 可以看出construct函数就是使用了全局的placement new方法在指定地址创建对象。

template 
void construct(Ty* ptr, Args&&... args)
{
  ::new ((void*)ptr) Ty(mystl::forward(args)...);
}

需要注意的是, placement new只是在指定的内存地址上构建对象,在对象使用完毕之后,需要手动调用析构函数做资源的析构。

char* buff = (char*)malloc(4096);
Stu *stu = new (buff) Stu("stu1", 10);
stu->print();
stu->~Stu();
free(buff);

为什么需要这样呢?

我们知道,使用delete关键字去释放一个对象时,首先会调用析构函数,然后再调用operator delete释放内存。

但是由于内存的申请并不是通过new对象而申请的,而是通过malloc申请到的一块内存空间,placement new只有"借用"了该内存空间去构建了对象, 因此是不能使用delete关键字去直接释放该对象的。由于不能直接调用delete stu,这就会导致对象的析构函数不会被自动调用,因此我们需要手动调用对象的析构函数做对象的析构stu->~Stu(),最后使用与malloc配套的free释放申请到的内存空间。

此外,我们讨论另外一个问题, placement new可以在栈上构建对象吗?

答案是肯定的。

#include 
#include 
#include 
using namespace std;

//student class
class Stu
{
public:
    Stu(string name, int age)
    {
        name_ = name;
        age_ = age;
    };
    ~Stu()
    {
        cout << "call destructor"  << endl;
    }
public:
    void print() const
    {
        cout << "name = " << name_ << std::endl;
        cout<< "age = " << age_ << std::endl;
    };
    void* operator new(size_t size, void* p)
    {
        std::cout << "placement new" << std::endl return p private: string name_ int age_ int main char buff4096 stu stu='new' buff stustu1 10 stu->print();
    stu->~Stu();
}

与在堆上调用placement new一样, 同样需要手动调用析构函数做资源的释放。但是由于内存是在栈上的, 因此不需要手动释放。

结论

对于new, operator new 和 placement new三者的区别, 我们总结如下:

new

new是一个关键字,不能被重载。

new 操作符的执行过程如下:

  1. 调用operator new分配内存 ;
  2. 调用构造函数生成类对象;
  3. 返回相应指针。

operator new

operator new就像operator + 一样,是可以重载的。如果类中没有重载operator new,那么调用的就是全局的::operator new来完成堆的分配。同理,operator new[]、operator delete、operator delete[]也是可以重载的。

placement new

placement new和operator new并没有本质区别。它们都是operator new操作符的重载,只是参数不相同。

placement并不分配内存,只是返回指向已经分配好的某段内存的一个指针。因此不能使用delete关键字删除它,需要手动调用对象的析构函数。

如果你想在已经分配的内存中创建一个对象,使用new时行不通的。也就是说placement new允许你在一个已经分配好的内存中(栈或者堆中)构造一个新的对象。原型中void* p实际上就是指向一个已经分配好的内存缓冲区的的首地址。

相关推荐

Vue3非兼容变更——函数式组件(vue 兼容)

在Vue2.X中,函数式组件有两个主要应用场景:作为性能优化,因为它们的初始化速度比有状态组件快得多;返回多个根节点。然而在Vue3.X中,有状态组件的性能已经提高到可以忽略不计的程度。此外,有状态组...

利用vue.js进行组件化开发,一学就会(一)

组件原理/组成组件(Component)扩展HTML元素,封装可重用的代码,核心目标是为了可重用性高,减少重复性的开发。组件预先定义好行为的ViewModel类。代码按照template\styl...

Vue3 新趋势:10 个最强 X 操作!(vue.3)

Vue3为前端开发带来了诸多革新,它不仅提升了性能,还提供了...

总结 Vue3 组件管理 12 种高级写法,灵活使用才能提高效率

SFC单文件组件顾名思义,就是一个.vue文件只写一个组件...

前端流行框架Vue3教程:17. _组件数据传递

_组件数据传递我们之前讲解过了组件之间的数据传递,...

前端流行框架Vue3教程:14. 组件传递Props效验

组件传递Props效验Vue组件可以更细致地声明对传入的props的校验要求...

前端流行框架Vue3教程:25. 组件保持存活

25.组件保持存活当使用...

5 个被低估的 Vue3 实战技巧,让你的项目性能提升 300%?

前端圈最近都在卷性能优化和工程化,你还在用老一套的Vue3开发方法?作为摸爬滚打多年的老前端,今天就把私藏的几个Vue3实战技巧分享出来,帮你在开发效率、代码质量和项目性能上实现弯道超车!一、...

绝望!Vue3 组件频繁崩溃?7 个硬核技巧让性能暴涨 400%!

前端的兄弟姐妹们五一假期快乐,谁还没在Vue3项目上栽过跟头?满心欢喜写好的组件,一到实际场景就频频崩溃,页面加载慢得像蜗牛,操作卡顿到让人想砸电脑。用户疯狂吐槽,领导脸色难看,自己改代码改到怀疑...

前端流行框架Vue3教程:15. 组件事件

组件事件在组件的模板表达式中,可以直接使用...

Vue3,看这篇就够了(vue3 从入门到实战)

一、前言最近很多技术网站,讨论的最多的无非就是Vue3了,大多数都是CompositionAPI和基于Proxy的原理分析。但是今天想着跟大家聊聊,Vue3对于一个低代码平台的前端更深层次意味着什么...

前端流行框架Vue3教程:24.动态组件

24.动态组件有些场景会需要在两个组件间来回切换,比如Tab界面...

前端流行框架Vue3教程:12. 组件的注册方式

组件的注册方式一个Vue组件在使用前需要先被“注册”,这样Vue才能在渲染模板时找到其对应的实现。组件注册有两种方式:全局注册和局部注册...

焦虑!Vue3 组件频繁假死?6 个奇招让页面流畅度狂飙 500%!

前端圈的朋友们,谁还没在Vue3项目上踩过性能的坑?满心期待开发出的组件,一到高并发场景就频繁假死,用户反馈页面点不动,产品经理追着问进度,自己调试到心态炸裂!别以为这是个例,不少人在电商大促、数...

前端流行框架Vue3教程:26. 异步组件

根据上节课的代码,我们在切换到B组件的时候,发现并没有网络请求:异步组件:...

取消回复欢迎 发表评论: