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

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

ztj100 2025-03-14 22:38 49 浏览 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实际上就是指向一个已经分配好的内存缓冲区的的首地址。

相关推荐

其实TensorFlow真的很水无非就这30篇熬夜练

好的!以下是TensorFlow需要掌握的核心内容,用列表形式呈现,简洁清晰(含表情符号,<300字):1.基础概念与环境TensorFlow架构(计算图、会话->EagerE...

交叉验证和超参数调整:如何优化你的机器学习模型

准确预测Fitbit的睡眠得分在本文的前两部分中,我获取了Fitbit的睡眠数据并对其进行预处理,将这些数据分为训练集、验证集和测试集,除此之外,我还训练了三种不同的机器学习模型并比较了它们的性能。在...

机器学习交叉验证全指南:原理、类型与实战技巧

机器学习模型常常需要大量数据,但它们如何与实时新数据协同工作也同样关键。交叉验证是一种通过将数据集分成若干部分、在部分数据上训练模型、在其余数据上测试模型的方法,用来检验模型的表现。这有助于发现过拟合...

深度学习中的类别激活热图可视化

作者:ValentinaAlto编译:ronghuaiyang导读使用Keras实现图像分类中的激活热图的可视化,帮助更有针对性...

超强,必会的机器学习评估指标

大侠幸会,在下全网同名[算法金]0基础转AI上岸,多个算法赛Top[日更万日,让更多人享受智能乐趣]构建机器学习模型的关键步骤是检查其性能,这是通过使用验证指标来完成的。选择正确的验证指...

机器学习入门教程-第六课:监督学习与非监督学习

1.回顾与引入上节课我们谈到了机器学习的一些实战技巧,比如如何处理数据、选择模型以及调整参数。今天,我们将更深入地探讨机器学习的两大类:监督学习和非监督学习。2.监督学习监督学习就像是有老师的教学...

Python教程(三十八):机器学习基础

...

Python 模型部署不用愁!容器化实战,5 分钟搞定环境配置

你是不是也遇到过这种糟心事:花了好几天训练出的Python模型,在自己电脑上跑得顺顺当当,一放到服务器就各种报错。要么是Python版本不对,要么是依赖库冲突,折腾半天还是用不了。别再喊“我...

超全面讲透一个算法模型,高斯核!!

...

神经网络与传统统计方法的简单对比

传统的统计方法如...

AI 基础知识从0.1到0.2——用“房价预测”入门机器学习全流程

...

自回归滞后模型进行多变量时间序列预测

下图显示了关于不同类型葡萄酒销量的月度多元时间序列。每种葡萄酒类型都是时间序列中的一个变量。假设要预测其中一个变量。比如,sparklingwine。如何建立一个模型来进行预测呢?一种常见的方...

苹果AI策略:慢哲学——科技行业的“长期主义”试金石

苹果AI策略的深度原创分析,结合技术伦理、商业逻辑与行业博弈,揭示其“慢哲学”背后的战略智慧:一、反常之举:AI狂潮中的“逆行者”当科技巨头深陷AI军备竞赛,苹果的克制显得格格不入:功能延期:App...

时间序列预测全攻略,6大模型代码实操

如果你对数据分析感兴趣,希望学习更多的方法论,希望听听经验分享,欢迎移步宝藏公众号...

AI 基础知识从 0.4 到 0.5—— 计算机视觉之光 CNN

...

取消回复欢迎 发表评论: