菜鸟笔记
提升您的技术认知

初识C++内存管理

1. new 和delete

在堆上开辟空间,C语言中我们要用到malloc,并且使用方式较为繁琐:

( int* )malloc(sizeof( int ) * 4)

且calloc/realloc 的方式也都相同,详细点击这里:动态内存管理分析理解

而在C++中使用 new 可以简化使用方式:

new 一个对象

new + 类型

int* p1 = new int;

初始化

new + 类型后面+(值)

int* p1 = new int(5);

 

new 多个对象

new + 类型 [个数]

int* p2 = new int[10];

初始化 

new + 类型 [个数] {初始化的值}

int* p3 = new int[10]{1, 2, 3, 4, 5, 6};

 

delete 释放

要注意格式匹配

像单个对象,直接delete 即可

delete p1;

如果是多个对象,delete + []

delete[] p2;
delete[] p3;

C++内存开辟释放和C中开辟释放有何区别? 

对于内置类型来说:new 和 malloc 并没有什么区别,只是用法不同;

对于自定义类型来说,new更加方便,会自动调用其构造函数,相当于开辟空间加初始化:

而利用初始化列表,创建构造函数,使用new ,可以大大简化这些步骤:

delete和free 对内置类型的处理方式也没什么区别

但是对于自定义类型而言

delete 对于自定义类型是会调用它自己的析构函数:

2. operator new与operator delete函数

这两个函数不是new 和 delete 的重载,只是名字的区别

new和delete是用户进行动态内存申请和释放的操作符

operator new 和operator delete是系统提供的全局函数

new在底层调用operator new全局函数来申请空间

delete在底层通过operator delete全局函数来释放空间

2.1 operator new

实际上operator new函数是malloc函数封装后的成果

此函数不会调用构造函数

和malloc相同点

都是函数、都会开辟空间

使用方法和malloc相同

 

和malloc不同点

通过malloc来申请空间,当malloc申请空间成功时直接返回,

申请空间失败,不会返回空,会抛异常;

这一点就跟new相同了

new底层原理

开辟空间,失败抛异常 + 调用构造函数

这两个功能合并到一块就构成了new

而开辟空间失败抛异常其实调用的就是封装malloc后的operator new函数。

operator new + 调用构造函数 = new

2.2 operator delete

实际上operator delete函数是free函数封装后的成果

此函数不会调用析构函数

为了跟operator new匹配对应

operator delete + 调用析构函数 = delete

2.3 operator new与operator delete的类专属重载

在项目中往往存在一直需要开辟空间的时候,就比如在链表中插入数据,每次new一个节点,都会在堆上申请,但是如果数据量较大,那么效率是会变低的,来看一个测试:

struct Listnode
{
	Listnode* _next;
	Listnode* _prev;
	int _data;
	Listnode(int data = 0)
		:_next(nullptr)
		, _prev(nullptr)
		, _data(data)
	{
		//记录节点数
		cout << "Listnode" << endl;
	}
};

class List
{
public:
	List()
	{
		_head = new Listnode;
		_head->_next = _head;
		_head->_prev = _head;
	}

	void Push(int val)
	{
		//每一次插入数据,都需要new一个节点
		Listnode* newnode = new Listnode;
		Listnode* tail = _head->_prev;

		//链接
		tail->_next = newnode;
		newnode->_prev = tail;
		newnode->_next = _head;
		_head->_prev = newnode;
	}

	~List()
	{
		Listnode* cur = _head->_next;
		while (cur!=_head)
		{
			Listnode* next = cur->_next;
			delete cur;
			cur = next;
		}
		delete _head;
		_head = nullptr;
	}

private:
	Listnode* _head;

};


int main()
{
	List l;
	int n = 1000;
	for (int i = 0;i<n;i++)
	{
		l.Push(i);
	}
}

为了提高速度,创建operator new与operator delete的类专属重载;

在构造节点时,会自动调用operator new的重载,清空时自动调用与operator delete的重载;

实现这两个重载函数,用到的是内存池,使用内存池申请和释放内存,提高效率

在之后会了解到,这里先主要是以重载为主,稍作了解:

struct Listnode
{
	Listnode* _next;
	Listnode* _prev;
	int _data;
	Listnode(int data = 0)
		:_next(nullptr)
		, _prev(nullptr)
		, _data(data)
	{
		//记录节点数
		cout << "Listnode" << endl;
	}


	void* operator new(size_t n)
	{
		void* p = nullptr;
		p = allocator<Listnode>().allocate(1);
		cout << "memory pool allocate" << endl;
		return p;
	}
	void operator delete(void* p)
	{
		allocator<Listnode>().deallocate((Listnode*)p, 1);
		cout << "memory pool deallocate" << endl;
	}
};

3. 定位new表达式

operator new 函数是调用 new 时的一部分,那我们单独调用operator new 进行空间开辟时,又不好初始化,因为构造函数不能显示调用。

使用格式

new (指针) + 类型;

new (指针) + 类型(初始化列表);

 

定位new什么时候用?

那么我们直接调用 new 不香吗,非得用operator new + 定位new?

定位new表达式在实际中一般是配合内存池使用:

因为内存池分配出的内存没有初始化,

所以如果是自定义类型的对象,

需要使用new的定义表达式进行显示调构造函数进行初始化