欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

C++ new、operator new和placement new的区别

程序员文章站 2024-01-29 19:23:22
...

new和delete是C++中的表达式,用于创建一个新的对象。它们是对堆中的内存进行申请和释放,而且这两个都是不能被重载的。要实现不同的内存分配行为,需要重载operator new和operator delete而不是new和delete。

1. 仔细说说new expression

在使用了new运算符之后,编译器实际做了哪些事情呢?

Foo* p = new Foo();
在这个代码中,这里的new实际上是执行如下3个过程:

1 调用operator new分配内存,大小为Foo对象所占用内存大小;

2 调用构造函数生成类对象;

3 返回相应指针。

C++ new、operator new和placement new的区别

Conclusion:因此在C++中使用new关键字相当于先申请内存后执行构造函数;使用delete相当于先执行析构函数后释放内存。

Question: C++对象实例化的时候使用new关键字和不适用new关键字的区别是什么?

Class Test { ... };
 
// 不使用new
Test test;
 
// 使用new
Test *ptr = new Test;

使用new是动态分配内存,这个分配的是堆的内存,需要用户自己手动释放,即通过delete释放;而不使用new的对象在栈的空间中,在当前作用于结束后自动回收。

new创建出的对象需要使用指针接收。

2. 再看看operator new和placement new

通过上面的图示,相信你也对new、operate new 和 placement new的关系有了一个感性的认识。

我这么形象的简单的介绍一下:

new相当于是封装好的一个最外层的关键字,程序员可以通过调用这个关键字新建对象,如果程序员A只知道使用new关键字而对其内部一无所知的话,那对他而言new关键字的功能就是能让他成功创建新对象的工具而已。而且我们基本不能更改new,因为他也不能重载。

operator new 和placement new则是在new背后默默付出的。在执行new的过程中,实际上有分三步:申请内存、调用构造函数和返回指针。

2.1 operator new

申请内存的过程其实就是调用了operator new,operator new默认情况下手下调用分配内存的代码,尝试得到一段堆上的空间,如果成功就返回,如果失败就调用一个new_handler,然后继续前面的过程。当然如果你觉得这个过程太复杂了,自己只想要申请空间的过程干干净净利利索索的,那就自己重载operator new这个函数,一般只能在类中进行重载。如果类中没有重载operator new,那么调用的就是全局的::operator new来完成堆的分配。

重载operator new/operator delete

class Test
{
public:
    // 在类中重载operator new
    void* operator new(size_t size)
    {
        printf("operator new called\n");
        return ::operator new(size);
    }
 
private:
    ...
};

全局的operator new也可以重载,不过这样以来就不能再递归的使用new来分配内存了,而只能使用malloc了。全局的重载很少见。

void* operator new(size_t size)
{
    printf("global new\n");
    return malloc(size);
}

如果你在类中重载了operator new和operator delete之后,我们使用new函数在编译器解析的时候就会像下图这样调用我们自己定义的operator new和operator delete(如果你在已经重载了的情况下还想使用全局的operator new,请使用::operator new)。
C++ new、operator new和placement new的区别

我的调试代码如下:


#include <iostream>
#include <stdlib.h>
using namespace std;
 
class Foo
{
public:
	Foo():id(0), name("null")
	{
		cout << "Ctor" << endl; 
		cout << id << "-" << name << endl;
	}
	
	Foo(int id, string name):id(id), name(name)
	{
		cout << "Ctor" << endl; 
		cout << id << "-" << name << endl;
	}
	
	~Foo()
	{
		cout << "Dtor" << endl;
	}
	
        // 如果在类中有重载的话编译器会调用以下的重载函数而不用全局函数
	static void* operator new(size_t);
	static void operator delete(void*, size_t);
	
private:
	int id;
	string name;	
};
 
static void* Foo::operator new(size_t size)
{
	cout << "MyOperatorNew" << endl;
	return malloc(size); 
}
 
static void Foo::operator delete(void* ptr, size_t size)
{
	cout << "MyOperatorDelete" << endl;
	free(ptr);
}
 
int main()
{
	Foo *foo = new Foo(2, "dae");
	delete foo;
	return 0;
}

2.2 placement new

在使用new关键字建立一个新的对象的时候,在编译器的第二步就是调用对象的构造函数生成类对象。这一步使用的就是placement new来实现的,即在取得了一块可以容纳指定类型对象的内存之后,在这块内存上构造一个对象。

如果要使用placement new, 需要包含头文件或者<new.h>

使用placement new的方法就像下面这样奇奇怪怪的:

p = new(raw) Foo();
当我们觉得默认的new operator对内存的管理不能满足我们的需要,而想自己手工管理内存时,就可以使用placement new了。

使用new操作符分配内存需要在堆中查找足够大的剩余空间,这个操作速度是很慢的,而且有可能出现无法分配内存的异常(空间不够)。placement new就可以解决这个问题。我们构造对象都是在一个预先准备好了的内存缓冲区中进行,不需要查找内存,内存分配的时间是常数;而且不会出现在程序运行中途出现内存不足的异常。所以,placement new非常适合那些对时间要求比较高,长时间运行不希望被打断的应用程序。

相关标签: C++