C++动态内存:(一)new/new[]、delete/delete[]运算符

一、为什么需要new?

   C语言中已经有了动态内存分配的函数malloc(链接:malloc/free的实现及malloc实际分配的内存)以及malloc的变体calloc/realloc(链接:C语言malloc/calloc/realloc)。在内置类型的动态创建上这些函数依然可以很好的工作。但是对于自定义类型和类这些函数将不能很好的运行。因为构造函数不允许我们向它传递内存地址进行初始化(构造函数不能被显示的调用--它是在对象创建时由编译器调用)。如果那么做了,将可能;

(1)忘记初始化。则在C++中有保证的对象的初始化将会难以保证。

(2)期望发生正确的事,但在对对象进行初始化之前意外地对对象进行了某种操作。

(3)把错误规模的对象传递给它。

   不正确的对象初始化会导致很多编程问题,因此,在堆上创建对象时,确保构造函数被调用特别重要。因为malloc与free是库函数,不在编译器控制范围。因此需要一个能够完成动态内存分配及初始化动作组合的运算符和另一个完成清理及释放内存组合动作的运算符,这样编译器就可以保证所有对象的构造函数和析构函数都会被调用。

  因此需要new和delete是运算符。

1、类对象的this指针会指向new返回的地址

2、默认的new还会进行检查以确信在传递地址给构造函数之前内存分配是成功的,所以不必显式的确定调用是否成功。

   注意:也可以使用malloc动态构建自定义类型,但是:1)需要自定义初始化和销毁的成员函数 2)需要在malloc返回后,手动调用初始化函数,在使用结束后手动调用销毁函数 3)调用free函数

二、运算符概述

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

1、new

(1)用new创建动态对象时会发生两件事:

     1)通过operator new()函数为对象分配内存(经常是调用malloc)

     2)调用构造函数来初始化内存。

(2)类对象的this指针会指向new返回的地址

(3)默认的new还会进行检查以确信在传递地址给构造函数之前内存分配是成功的,所以不必显式的确定调用是否成功。

(4)如果使用了new的内存分配没有成功,构造函数不会被调用。(因此创建对象指针时,最好将其初始化为0)

(5)如果第一个函数(operator new)调用成功,第二个函数(构造函数)却抛出异常,则系统会调用第一个函数对应的operator delete版本。因此若重载一个带额外参数的operator new,那么也要定义一个带相同额外参数的operator delete。

示例:

#include<iostream>
#include<new>//bad_alloc definition
using namespace std;
class Widget
{
	int i[10];
public:
	Widget(){cout<<"Widget()::Widget()";}
	~Widget(){cout<<"Widget()::~Widget()";}
	void* operator new(size_t sz) throw(bad_alloc)
	{
		cout<<"Widget::new: "<<sz<<"bytes"<<endl;
		throw bad_alloc();
		return ::new char[sz];
	}
	void operator delete(void* p)
	{
		cout<<"Widget::delete "<<endl;
		::delete []p;
	}

};
int main( )
{	
	Widget *w=0;
	try
	{
		w=new Widget;
	}
	catch(bad_alloc)
	{
		cerr<<"Bad_alloc Eception!"<<endl;
		return 0;
	}
	delete w;
	return 0;
}
输出(构造函数没有被调用):

 2、delete

相对应的调用delete运算符会

(1)首先调用析构函数

(2)通过operator delete()释放内存(经常是调用free)

注意:我们无法控制构造函数和析构函数的调用,是由编译器调用的。

3、delete[ ]

对于对象数组的删除一定要使用delete[]

因为:单一对象的内存布局一般而言不同于数组的内存布局。数组通常包括数组大小的记录(以便知道需要调用多少次析构函数),而单一对象通常没有。

三、注意事项

1、new创建的对象要用delete删除。malloc创建的对象要用free删除。不能混用。如果用free来删除new创建的对象,则可能会没有调用析构函数。用delete来删除malloc创建的对象是未定义的。

2、如果delete的对象的指针为0,则不发生任何事情。因此,在删除指针后,可以立即把它赋值为0,以免对它删除两次。

3、delete一个void指针,唯一发生的就是释放了内存,这是因为即没有类型信息,也没有办法让编译器知道要调用哪个析构函数。因此,如果在程序中发现内存丢失的情况,可以搜索所有的delete语句检查被删除指针的类型。如果是void*类型,则可能发现了引起内存丢失的某个因素。

示例:

#include <iostream>
using namespace std;

class Object
{
private:
	void *data;
	const int size;
	const char id;
public:
	Object(int sz,char c):size(sz),id(c)
	{
		data=new char[size];
		cout<<"Constructing object "<<id<<",size="<<size<<endl;
	}
	~Object()
	{
		cout<<"Destructing object "<<id<<endl;
		delete[] data;
	}

};
int main( )
{
	Object *a=new Object(40,'a');
	delete a;
	void *b=new Object(50,'b');
	delete b;
	return 0;
}
结果(delete一个void类型不会调用析构函数):


4、用于数组的new,要用delete[]

class myType{};
myType* fp=new myType[100];
myType* fp2=new myType;
delete fp[];
delete fp2;
为了防止出错,可以使用const是指针更像数组 :

int *const q=new int[100];

特别注意的是对于自定义数组形式最好不要使用typedef,因为很容易造成delete不匹配的情况。

typedef std::string line[4];

std::string *pa=new line;
delete pa;//错误

5、对于new的单一对象,不可以使用delete[]。因为结果为定义。使用delete[]时,delete会读取若干内存并将它解释为“数组大小”,然后多次调用析构函数,而单一对象在内存没有这块内存。因此使用delete[]释放单一对象的内存时会出错。

6、在一个容器里,不要既有指向在栈上创建的对象的指针,又有指向在堆上创建的内存的指针。因为这样,取回一个指针时,我们不知道是它所指向的对象是被分配到栈上的还是堆上的。进行内存释放时,会出现错误。

7、如果第一个函数(operator new)调用成功,第二个函数(构造函数)却抛出异常,则系统会调用第一个函数对应的operator delete版本,若没有则系统什么都不会做,会发生内存泄露。因此若重载一个带额外参数的operator new,那么也要定义一个带相同额外参数的operator delete。如果希望这些函数有着平常行为,只要令专属版本调用global版本即可。

     但是,如果没有在构造函数里抛出异常,则placement delete不会被调用。delete一个指针,调用的是正常形式的delete。

四、重载new和delete

链接:重载new和delete

五、C++  new_handler和set_new_handler

链接:C++ new_handler和set_new_handler


参考资料:

1、《C++编程思想》

2、《Effective C++》





猜你喜欢

转载自blog.csdn.net/zxx910509/article/details/63679774