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

el/2024/4/13 15:13:12

3 C ++中动态内存创建

3.1 new operatordelete 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 newoperator delete

3.2.1 new operatoroperator 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 operatorstring *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 newoperator 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 newoperator new[],那么就得同时提供对应的类相关版本的plain newplacement new以及nothrow new,否则根据名字隐藏的规则(将全局new遮掩掉了),将会发生没有可用匹配的错误。

一旦为类重载了operator newoperator delete,那么无论何时创建这个类的对象,都将调用这些重载的运算符,但若创建该类的一个对象数组时,全局operator new将立即被调用,用来为这个数组分配足够的内存,因此要避免出现这种情况,需要重载operator new的数组版本:operator new[]operator delete[]

演示示例:(item 36 in exceptional c++)

1.             //问题1:为什么Bdelete有第二个参数而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则通过Cmalloc方式报告失败,即仅返回空指针,永远不会抛出异常。

B 内存分配失败的处理过程:通过set_new_handler调用错误处理函数new_handler,检查指向函数的指针,若指针非0,则指向的函数被调用。

new_handlerset_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];

       ...

}

 

http://www.ngui.cc/el/4967617.html

相关文章

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

4 内存分配与管理 4.1 内存管理策略 A 通用意图的分配策略――提供调用方可能要求的任意大小的内存块&#xff0c;非常灵活但造成内存碎片以及性能较低&#xff1b; B 固定大小的分配策略――返回固定大小的内存块&#xff0c;性能高碎片少但是灵活性差&#xff1b; C 垃圾收…

Inside the C++ Object Model学习笔记[Chap3.5-3.6]

3.5 成员对象的效率 简要列出作者测试的几个策略&#xff1a; 1. 针对三个float元素做组成的局部数组&#xff1b; 2. 把上面的数组元素转化为C中的结构形式&#xff1b; 3. 定义独立的类进行存取&#xff1a;① 用inline函数传回引用的方式&#xff1b;② 返回值的方式&…

Inside the C++ Object Model学习笔记[Chap4.0-4.2]

第4章 函数语意学 4.0 引言 对Point3D的指针和对象&#xff1a; Point3D opd; Point3D *ppd; 调用其成员函数时&#xff1a; opd.normalize(); ppd->normalize(); 将有什么动作发生及其区别&#xff1f;显然根据成员函数的三种不同方式&#xff1a;static&#xff0c;nonsta…

Inside the C++ Object Model学习笔记[Chap4.3-4.4]

4.3 指向成员函数的指针 取一个非静态数据成员的地址&#xff0c;得到的是该成员在类布局中的偏移量&#xff08;未优化时还需要加1&#xff09;&#xff0c;它需要被绑定到某个类对象的地址上才可以存取&#xff1b;而取一个非静态成员函数的地址&#xff0c;若为非虚函数&…

Inside the C++ Object Model学习笔记[Chap5]

第5章 构造、析构与拷贝语意学 5.0 引言 可以定义和调用一个纯虚函数&#xff0c;但是只能被静态地调用&#xff0c;不能经由虚拟机制调用&#xff0c;如&#xff1a; class AbstractBase { public: virtual void interface() const 0; }; inline void AbstractBase::interfac…

广东北电笔试[2006.10.20]

题目&#xff1a; 1 翻译 &#xff08;10分&#xff09; 英译中&#xff1a;关于协议与有线无线连接的问题&#xff1b; 中译英&#xff1a;程序设计中关于函数参数列表的问题。 2 设计一个函数&#xff0c;实现阶乘运算&#xff0c;但是需要考虑溢出问题。&#xff08;3…

索贝数码笔试[2006.10.24]

综合看来&#xff0c;索贝的题目分三个主要部分&#xff1a; 1、基础部分&#xff1a; A、选择题&#xff1a;音视频基础知识&#xff0c;网络基础知识&#xff0c;基本C语言知识 &#xff1b; B、填空题&#xff1a;给一个链表&#xff0c;根据其函数填写一些语句&#xff1b…

锐捷网络笔试[2006.10.25]

软件开发类题目主要分三个部分&#xff1a; 1、ansi c部分&#xff1a;考的很基础&#xff0c;但是不代表能够做对&#xff0c;我本以为我的c基础还行&#xff0c;但是做完题目之后才发现自己还差的很远&#xff5e;&#xff5e;&#xff5e; 指针&#xff0c;基本定义&#x…

索贝数码一面[2006.10.25]

早上8&#xff1a;00到电子科大参加锐捷网络的笔试&#xff0c;刚回到实验室接到索贝数码的面试通知&#xff0c;让到电子科大通信楼去面试&#xff0c;我当时就晕了&#xff5e;&#xff5e;&#xff5e;&#xff5e;早知道参加锐捷测试的笔试&#xff0c;那样的话我就不用再跑…

重邮信科笔试[2006.10.26]

我投的职位是设计公司的交换网络咨询师以及综合部门的网络管理&#xff0c;得到的是设计公司的有线网络部分试题&#xff0c;整个试题主要考No.7信令系统&#xff0c;SDH网络&#xff0c;光纤特性&#xff0c;误码这些方面的问题&#xff5e;&#xff5e;&#xff5e;&#xff…