【c++】——new和delete

一、new和delete的基本情况

说到new和delete以及malloc和free大家已经很熟悉了吧,在我之前的一篇博文中也有提过相关的知识点,详情请见malloc和new

1、malloc和new的区别

关于malloc和new的区别主要有三点

  1. malloc按字节开辟内存的;new开辟内存时需要指定类型 new int[40],所以malloc开辟内存返回的都是void*,operator new -> int*

  2. .malloc只负责开辟空间,new不仅仅有malloc功能,可以进行数据的初始化
    其中new有两种方式开辟空间。例如:new int(20);–>给初值 new int20;–>指定元素个数并初始化为0

  3. malloc开辟内存失败返回nullptr指针;new抛出的是bad_alloc类型的异常

以下代码是判断内存开辟失败的方法:

try
	{
		int* p = new int;
		delete p;
	}
	catch (const bad_alloc & err)
	{
		cerr << err.what() << endl;
	}

记住,不能使用if(p == nullptr)

2、malloc和new、free和delete的区别

(1)free和delete的区别
delete:调用析构函数,再free。但是delete(int*)p对于整形来说只剩内存释放了
因为delete的底层实现如下:

void operator delete(void* ptr)
{
	cout << "operator delete addr:" << ptr << endl;
	free(ptr);
}

如果delete p;(p是指向对象的指针)调用p指向对象的析构函数,再调用operator delete释放内存空间。
但是如果p是一个整形,不存在外部堆空间,不需要析构函数,delete和free的作用一致

扫描二维码关注公众号,回复: 10944508 查看本文章

(2)malloc和new的区别
他们的区别和free和delete同理。
new的底层实现如下:
先调用operator new开辟内存空间,然后调用对象的构造函数(初始化)

void* operator new(size_t size)
{
	void* p = malloc(size);
	if (p == nullptr)
		throw bad_alloc();
	cout << "operator new addr:" << p << endl;
	return p;
}

3、new和delete能混用吗?c++为什么区分单个元素和数组的内存分配和释放?

(1)结论
一般情况下:要求new delete、new[] delete[]配对使用
特殊的:

  1. 对于普通的编译器内置类型 可以混用
  2. 自定义的类的类型不能混用有析构函数,为了调用正确的析构函数,那么开辟对象数组的时候会多开辟4个字节,记录对象的个数

(2)原理
首先,我们来看一下数组new和delete底层实现如下:

void* operator new[](size_t size)
{
	void* p = malloc(size);
	if (p == nullptr)
		throw bad_alloc();
	cout << "operator new[] addr:" << p << endl;
	return p;
}

void operator delete[](void* ptr)
{
	cout << "operator delete[] addr:" << ptr << endl;
	free(ptr);
}

再者,我们来写一个Test对象使用

class Test
{
public:
	Test(int data = 10)  { cout << "Test()" << endl; }
	~Test() {  cout << "~Test()" << endl; }
private:
	int ma;
};

当我们在main函数这样使用的时候

int main()
{
    Test* p2 = new Test[5];
	cout << "p2:" << p2 << endl;
	delete[] p2;
	return 0;
}

完全没有问题,输出如下:
在这里插入图片描述
但是,当我们在main函数这样使用的时候

Test* p2 = new Test[5];
	cout << "p2:" << p2 << endl;
	delete p2;

却出现了问题:
在这里插入图片描述
原因是什么呢?
我们首先来分析一下,这段代码,因为delete调用析构函数,析构函数有this指针知道析构对象地址,但是不知道要析构多少个对象,所以开辟内存的时候多开辟4个字节存储对象的个数,如下图所示:
在这里插入图片描述
所以说,在开辟的时候我们多开辟了四个字节,但是从上述错误结果看,返回的时候就少了四个字节,所以报错。

二、对象池

说到对象池我们首先来写一个链队列的实现:

#include<iostream>
using namespace std;

template<typename T>
class Queue
{
public:
	Queue()
	{
		_front = _rear = new QueueTtem();
	}
	~Queue()
	{
		QueueTtem* cur = _front;
		while (cur != nullptr)
		{
			_front = _front->_next;
			delete cur;
			cur = _front;
		}
	}
	void push(const T& val)//入队操作
	{
		QueueTtem *item = new QueueTtem(val);
		_rear->_next = item;
		_rear = item;
	}
	void pop()
	{
		if (empty())
			return;
		QueueTtem* first = _front->_next;
		_front->_next = first->_next;
		if (_front->_next == nullptr)//只有一个结点的时候
		{
			_rear = _front;
		}
		delete first;
	}
	T front() const
	{
		return  _front->_next->data;
	}
	bool empty()const { return _front == _rear; }
private:
	struct QueueTtem
	{
		QueueTtem(T data = T()):_data(data),_next(nullptr){ }
		T _data;
		QueueTtem* _next;
	};

	QueueTtem* _front;//指向头结点
	QueueTtem* _rear;//指向队尾

};
int main()
{
	Queue<int> que;
	for (int i = 0; i < 1000000; ++i)
	{
		que.push(i);
		que.pop();
	}
	cout << que.empty() << endl;
	return 0;
}

在主函数里面我们实现对1000000个数据的出队和入队操作如下:

int main()
{
	Queue<int> que;
	for (int i = 0; i < 1000000; ++i)
	{
		que.push(i);// QueueTtem(i)
		que.pop();//QueueTtem
	}
	cout << que.empty() << endl;
	return 0;
}

但是仔细思考一下上述代码,似乎有些不妥。因为我们在
短时间内大量调用malloc和free,对程序性能有所影响。那么怎么来解决这个问题呢?
我们引出了对象池的概念。它的主要功能就是对QueueTtem提供自定义的内存管理。
里面有数据域和指针域,以链式的方式将所有结点链接到一起,效果如下:
在这里插入图片描述
下面是具体实现
(1)首先在类外定义一个池的入口结点

template<typename T>
typename Queue<T>::QueueTtem* Queue<T>::QueueTtem::_itemPool = nullptr;

注意:如果没有typename,编译会出错,因为到这一步的时候,还没有初始化,不知道是一个什么,*到底是处理成指针还是乘法还不清楚、
所以typename 的作用就是告诉编译器后面嵌套类的名字是个类型
(2)new运算符的实现

void* operator new(size_t size)
		{
			if (_itemPool == nullptr)
			{
				_itemPool = (QueueTtem*)new char[POOL_ITEM_SIZE * sizeof(QueueTtem)];//按字节开辟
				QueueTtem* p = _itemPool;
				for (; p < _itemPool + POOL_ITEM_SIZE-1; ++p)
				{
					p->_next = p + 1;
				}
				p->_next = nullptr;//最后一个结点的地址域
			}
			//表示池里面是有结点的,则找到入口结点即可
			QueueTtem* p = _itemPool;
			_itemPool = _itemPool->_next;
		}

(2)delete运算符的实现

void* operator delete(size_t size)
		{
			QueueTtem* p = (QueueTtem*)ptr;
			//归还过程就是把它加到第一个结点后面即可
			p->_next = _itemPool;
		}

假设,现在内存池的状态如下,p+1结点此刻想要归还结点
在这里插入图片描述
在这里插入图片描述
把想归还结点的_next域加到_itemPool指向的结点后面,再改变_itemPool的指向,让其指向想归还结点即可。

有了内存池过后,上述链队列的实现如下:

#include<iostream>
using namespace std;

template<typename T>
class Queue
{
public:
	Queue()
	{
		_front = _rear = new QueueTtem();
	}
	~Queue()
	{
		QueueTtem* cur = _front;
		while (cur != nullptr)
		{
			_front = _front->_next;
			delete cur;
			cur = _front;
		}
	}
	void push(const T& val)//入队操作
	{
		QueueTtem *item = new QueueTtem(val);
		_rear->_next = item;
		_rear = item;
	}
	void pop()
	{
		if (empty())
			return;
		QueueTtem* first = _front->_next;
		_front->_next = first->_next;
		if (_front->_next == nullptr)//只有一个结点的时候
		{
			_rear = _front;
		}
		delete first;
	}
	T front() const
	{
		return  _front->_next->data;
	}
	bool empty()const { return _front == _rear; }
private:
	struct QueueTtem//产生一个QueueTtem的对象池(10000个QueueTtem节点)
	{
		QueueTtem(T data = T()):_data(data),_next(nullptr){ }
		//对QueueTtem提供自定义的内存管理,这两个函数都是静态的方法因为使用的时候都还没有对象
		void* operator new(size_t size)
		{
			if (_itemPool == nullptr)
			{
				_itemPool = (QueueTtem*)new char[POOL_ITEM_SIZE * sizeof(QueueTtem)];//按字节开辟
				QueueTtem* p = _itemPool;
				for (; p < _itemPool + POOL_ITEM_SIZE-1; ++p)
				{
					p->_next = p + 1;
				}
				p->_next = nullptr;//最后一个结点的地址域
			}
			//表示池里面是有结点的,则找到入口结点即可
			QueueTtem* p = _itemPool;
			_itemPool = _itemPool->_next;
		}
		void* operator delete(size_t size)
		{
			QueueTtem* p = (QueueTtem*)ptr;
			//归还过程就是把它加到第一个结点后面即可
			p->_next = _itemPool;
		}
		T _data;
		QueueTtem* _next;
		static QueueTtem* _itemPool;
		static const int POOL_ITEM_SIZE = 1000000;

	};

	QueueTtem* _front;//指向头结点
	QueueTtem* _rear;//指向队尾

};
template<typename T>
typename Queue<T>::QueueTtem* Queue<T>::QueueTtem::_itemPool = nullptr;
int main()
{
	Queue<int> que;
	//短时间内大量调用malloc和free,对程序性能有所影响
	for (int i = 0; i < 1000000; ++i)
	{
		que.push(i);// QueueTtem(i)
		que.pop();//QueueTtem
	}
	cout << que.empty() << endl;
	return 0;
}
发布了98 篇原创文章 · 获赞 9 · 访问量 3666

猜你喜欢

转载自blog.csdn.net/qq_43412060/article/details/105156837