您的位置:首页 > 编程语言 > C语言/C++

C++动态内存创建与内存管理学习笔记[3]

2006-10-21 10:20 791 查看

3 C++中动态内存创建

3.1 new operator与delete operator

C++中动态内存创建new operator分为两个步骤:在堆里为对象分配内存(C++中的operator new具有内置的长度计算,类型转换与安全检查);如果内存分配成功,则为该内存调用合适的构造函数进行初始化。
new operator实际上总以标准C中的malloc()完成,同时delete operator也是以标准C中的free()完成。[参见Inside the C++ Object Model]
delete operator也相应地分为两步:调用相应类的析构函数;释放内存。
注意:delete一个void指针,唯一发生的是释放了内存,因为没有类型信息也没有办法让编译器知道要调用的是哪个析构函数,而delete一个NULL指针,则是安全的,因为它什么都没有作。
Delete后指针并不会自动清除为0,指针所指对象的生命期因delete而结束,尽管delete指针后该地址上的对象不再合法,但地址本身却仍代表一个合法的程序空间,对它进行操作将没有定义,因此建议delete指针后将指针赋值为0,这样避免对它删除多次或误用。
演示示例:(由伪码可以清晰地看出两个步骤)
Point3d *origin = new Point3d;
delete origin;
其伪码可能为:
Point3d *origin;
if ( origin = __new( sizeof( Point3d ))) //分配空间
origin = Point3d::Point3d( origin ); //调用构造函数初始化
if ( origin != 0 )
{
Point3d::~Point3d( origin ); //调用析构函数
__delete( origin ); //释放空间
}

3.2 operator new与operator delete

3.2.1 new operator与operator new的区别

String* ps = new string(“string”);其中的new表示new operator,由语言内建,不能改变其意义,总是作相同的事情:分配足够的内存,用于存放某类型的对象;调用构造函数,为刚分配的内存中的对象设定初值。返回指向特定类型的指针,完成了对象的创建。程序员不能改变其行为,只能改变用来容纳对象的那块内存的分配行为。New operator调用某个函数,执行必要的内存分配动作,可以重写或者重载那个函数,改变其行为,该函数就是operator new;
operator new的通常声明为void* operator new(size_t size);它返回一个void指针,指向一块生鲜的,没有初值的内存。Operator new唯一的任务就是分配内存,取得operator new返回的内存并将之转换为一个对象,是new operator的责任,示例:
new operator:string *ps = new string("Memory Management");
转化为operator new:
void *memory = operator new(sizeof(string)); //得到未经处理的内存为String对象
  call string::string("Memory Management") on *memory; //初始化内存中的对象
  string *ps = static_cast<string*>(memory); //是ps指针指向新的对象

3.2.2 标准中支持的三种new形式

A plain new(简单new):即一般所用的new,他不接受任何的额外参数;plain new抛出一个异常的类型std::bad_alloc。这个是标准适应性态。在早期C++的舞台上,这个性态和现在的非常不同――new将返回0来指出一个失败,和malloc()非常相似。
B nothrow new:可以接受额外参数;在一定的环境下,返回一个NULL指针来表示一个失败依然是一个不错的选择。C++标准委员会意识到这个问题,所以他们决定定义一个特别的new操作符版本nothrow new,返回0表示失败。一个nothrow new语句和普通的new语句相似,除了它的变量将涉及到std::nothrow_t。
C placement new:在内存的指定位置上构造一个对象,此举不会分配任何新空间,使用placement new相当于一次显式构造函数调用。注意在一个已存在的对象上调用构造函数是没有意义的,因为构造函数用来初始化对象,而一个对象仅仅能在给它初值时被初始化一次。Placement new用于在一些已被分配但是尚未处理的的raw内存中构造一个对象。
注意:使用了placement new时,需要显式调用析构函数,如t->T::~T();
三种new的原型声明如下:
Plain new: void* ::operator new(std::size_t sz) throw (std::bad_alloc);
Nothrow new: void* ::operator new(std::size_t sz, const std::nothrow_t &nt) throw();
Placement new: void* ::operator new(std::size_t sz, void* ptr) throw();
其用法的列表比较如下:
Operator new
额外的形参
是否进行内存分配
是否可能失败
抛出异常
是否可替换
Plain


是(抛出)
Std::bad_alloc

Nothrow
Std::nothrow

是(返回)


Placement
Void*




Operator new一般不支持多态。
演示示例需要

3.3 重载operator new与operator delete

重载operator new的问题:对于new operator,能够改变的只有operator new部分,即只能改变原有的内存分配方式,而不能改变为初始化该内存而调用构造函数部分。
重载operator new的方面:分配内存的方式;分配失败时所需作的事情:返回与抛出异常等。

3.3.1 重载全局new/delete

重载全局new 后,使默认版本完全不能被访问,甚至在这个重新定义里也不能调用他们,因此在使用时需要慎重考虑。
重载的operator new必须含有一个size_t参数,表示要分配内存的对象的长度,同时必须返回一个指向等于这个长度的对象的指针。若没有找到符合要求的存储单元,构造函数将不被调用,另外需要一个表示失败的返回值以外还应该抛出异常(可以自定义)。
Operator delete参数为一个指向由operator new分配内存的void*(已调用析构函数后得到的指针),其返回类型为void。
演示示例

3.3.2 重载类中的new/delete

重载类中的new实际上创建的是一个static成员函数,该new只为创建该类对象起作用,不会影响默认的全局版本new,但是需要注意名字隐藏的问题。
注意:任何类中只要提供了自己的operator new或operator new[],那么就得同时提供对应的类相关版本的plain new,placement new以及nothrow new,否则根据名字隐藏的规则(将全局new遮掩掉了),将会发生没有可用匹配的错误。
一旦为类重载了operator new与operator delete,那么无论何时创建这个类的对象,都将调用这些重载的运算符,但若创建该类的一个对象数组时,全局operator new将立即被调用,用来为这个数组分配足够的内存,因此要避免出现这种情况,需要重载operator new的数组版本:operator new[]与operator delete[]。
演示示例:(item 36 in exceptional c++)
1.             //问题1:为什么B的delete有第二个参数而D没有?

2.             class B

3.             {

4.             public:

5.               virtual ~B();

6.               void operator delete  ( void*, size_t ) throw();

7.               void operator delete[]( void*, size_t ) throw();

8.               void f( void*, size_t ) throw();

9.             };

10.          class D : public B

11.          {

12.          public:

13.            void operator delete  ( void* ) throw();

14.            void operator delete[]( void* ) throw();

15.          };

16.          void f()

17.          {

18.            //问题2:下面各个语句中,调用的是哪一个delete以及调用时的参数,为什么?

19.       D* pd1 = new D;

20.       delete pd1;

21.       B* pb1 = new D;

22.       delete pb1;

23.       D* pd2 = new D[10];

24.       delete[] pd2;

25.       B* pb2 = new D[10];

26.       delete[] pb2;

27.

28.       //问题3:下面两个赋值语句合法吗?

29.       typedef void (B::*PMF)(void*, size_t);

30.       PMF p1 = &B::f;

31.       PMF p2 = &B::operator delete;

32.     }

3.4 内存分配失败问题

A 内存分配失败的报告方式:大多数new通过抛出bad_alloc异常来报告分配失败;nothrow new则通过C中malloc方式报告失败,即仅返回空指针,永远不会抛出异常。
B 内存分配失败的处理过程:通过set_new_handler调用错误处理函数new_handler,检查指向函数的指针,若指针非0,则指向的函数被调用。
new_handler与set_new_handler的原型如下:
typedef void (*new_handler)();
new_handler set_new_handler(new_handler p) throw();
new_handler是一个typedef,表现出一个函数指针,该函数没有参数也没有传回值,而set_new_handler是一个函数,需要一个new_handler参数并传回一个new_handler。
Set_new_handler的参数指针指向的函数正是当operator new无法配置足够内存时,应该去调用的函数,其传回值是一个函数指针,指向先前登陆过的new_handler。用法示例如下:
void nomorememory()
{
cerr << "unable to satisfy request for memory/n";
abort();
}
int main()
{
set_new_handler(nomorememory);
int *pbigdataarray = new int[100000000];
...
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: