C++中的new和delete是如何在堆上安全地创建对象
为对象分配内存的三种方式:
1)
静态内存分配
在程序开始之前就已分配好了存储空间,直到程序结束
2)
在栈上分配内存
当程序运行到一个执行点(譬如以“
{
”开始的一个程序块),存储空间在栈上被创建,当出这个这程序块时,自动释放。如果为一些局部变量分配存储单元一样。这种分配方法分配的存储空间大小是固定的,且要在程序中明确指定所要分配的存储空间的大小。
(
对象的大小、生存期被内置在编译器所生成的代码里
)
3)
在堆上动态分配内存
是由程序在执行的过程中根据需要在堆上动态分配的,它的大小可根据需要变化。需要程序员手动释放这些空间。生存期不受所在存储块范围的影响。(要比在栈上分配内存更多开销)
(
C++
不允许直接将
void*
型指针赋给任何其它类型的指针,要想赋值必须先进行强制类型转换。)
C++
中类的构造函数不能被显式调用,而在创建对象由编译器调用的。
operator new
所做的事情:
1)
计算并分配所需的存储空间;
2)
检查是否成功地分配了存储空间;
3)
将所分配的存储空间进行类型转换(转换为所分配给的那个类类型),返回指向所分配存储空间的指针给类的构造函数。
它使得在堆里和在栈里创建一个对象一样容易。
用于普通单个对象的
new:
delete
一个
void*
指针,将不会调用任何析构函数。因为它不确定是什么类型的指针也不确定调用哪个析构函数。执行这个操作可能引起一个错误。
如果一个类里面有
void*
类型的指针,那么在
delete
时,不会释放
void*
的指针所指向的内存,这样就会引起内存泄露。为避免这种现象,可以在
delete
之前所它转换成非
void*
的合适的类型的指针。
如果类的构造函数没有参数,那么可以写没有构造函数参数表的
new
表达式(如:
A *a=new A;
)。
我想这也就解释了:为什么用
new
动态创建一个对象时,总是先定义一个类对象指针,然后用
new
表达式并赋值给这个指针?为什么有时候
new
表达式后只是一个构造函数名,有时是一个有参数表的构造函数名,有时又跟是一个空参数表的构造函数名
?
class A{
A();
};
A *a=new A;
和
A *a=newA();
是一样的了。
用于数组的
new
:
要求:必须有一个默认的构造函数,以方便对数组里每一个对象都调用不带参数的构造函数。
A *a=new A[100];//
那么
new
表达式将返回一个数组指针赋值给
a
,这个数组是一个由指向
A
类对象的指针为元素的数组,也就是说
a
是一个
(
包含
100
个
(
指向
A
类对象的指针
)
元素的
)
数组指针。所以在释放
a
所指向的内存时,就不能像
delete
普通对象那样,而是要
delete
一个对象指针数组指针。具体看
operator delete
说明。
由于一个指针变量可以改为指向任意其它类型,而对于如上所说的数组指针
a
的修改没有意义,且会引起一些错误,所以为避免这种情况的发生,我们可以将指针
a
定义为一个指针常量
(A* const a=new A[100];)
,这样也不会数组里的对象指针有影响。(注意:不能写成了
A const *p=new A[100];
或
const A *p=new A[100];
,这样就成了让数组里的元素
(
对象指针
)
为常量,而数组指针仍然是变量。)
在用
new
为一个对象分配内存时,如果没有连续的足够大的内存分配给这个对象,那就将会调用
new-handle()
函数,抛出一个异常。
operator delete
delete
表达式所做的事情:
1)
取回创建数组时所记下的对象数;
(
如果是一个对象数组
,
如:
delete [ ]a;
,由
[ ]
告诉编译器需要做这件事
)
2)
调用每个对象的析构函数
3)
释放为每个对象所分配的存储空间(一般是用
free()
)
*
如上第1)步只在对象数组时才需要,如果是普通的单个非数组对象指针则不需要这一步
delete
需要一个对象的地址(指针)。它只放释放
new
所分配的存储空间,如果用
delete
释放一个
malloc()
或
calloc()
的存储空间,动作是未定义的。
delete
释放空间之后,并不会自动把指向它的指针赋值为空,为避免对同一空间多次释放,一般在调用释放之后(在这里就是
delete
之后)手动将所指向它的指针赋值为0,而对一个空指针多次释放是不会有问题的(因为释放一个空指针什么也不发生)。
delete a;
a=0;
重载
new
和
delete:
这样做只是改变
new/delete
表达式里内存分配和释放部分,不也无法改变调用构造函数和析构函数的部分。
全局的
new/delete
重载:
对全局
new()/delete()
的重载将使默认的
new()/delete()
完全不能被使用,甚至连这个重载版本里也不能被使用。
new()
的参数:必须有一个
size_t
类型的参数,它由编译器传递给我们,是所要分配内存空间的对象的大小,在使用
new
时通常看不这个参数的显式传递;
new()
的返回值:一个
void*
类型的指针(如果分配内存不成功,将返回
0
)。
在函数里面应该对内存分配不成功做处理(如调用
new-handle()
或抛出异常或重新分配内存等等之类)。
delete()
的参数:一个
void*
指针(这是因为先调用了析构函数后得到的指针),
delete()
的返回值:返回类型为
void
。
一个类的
new/delete
重载:
在一个类里重载的
new/delete
只是对当前这个类默认不使用全局的
new/delete
,在其它地方其它类里面仍然可使用全局的
new/delete
。但是,对于已重载
new/delete
的类,如果创建它的对象数组,仍然使用的是全局的
new/delete
,除非也对它重载了创建对象数组版本的
new[]/delete[ ]
。
重载
new
和
delete
的其它用处:
1)
把对象放在指定的内存位置上,这种方法可以在
new()
的参数中指定对象所放的位置,也可以加其它更多的参数;
2)
采用特定的内存分配方案方案。
显式调用析构函数会引起一些问题:
1
)对于栈上创建的对象,会导致两次调用对象的析构函数(因为在栈对象作用域结束的位置会自动
destroy
对象);
2
)对于堆上创建的对象,显式调用析构函数后并没有释放内存。所以在这两种情况下都不显式调用析构函数,要显式调用析构函数,只有一种情况:支持
operator new()
的定位语法时,也就是用于把对象放在指定的内存位置的情况。