改善c++程序的150个建议(读后总结)-------27-35
阅读原文时间:2023年07月08日阅读:2

27. 区分内存分配的方式

c++中内存分为5个不同的区

①栈区

栈是一种特殊的数据结构,其存取数据特点为(先进后出,后进先出)。栈区中主要用于存储一些函数的入口地址,函数调用时的实参值以及局部变量。栈区的大小和内存分配由编译器和底层硬件配合完成在函数调用时发挥很大作用,但是程序员不需要关心其的操作(因此起较安全)。

②堆区

堆区主要是用户自由分配内存的区域,new(malloc())与delete(free())运算符都是在操作这块区域。因为堆区中内存的申请和释放都是交给程序员自己完成所以比较危险,容易出错。在程序结束后所有没有释放的区域都会被操作系统自动回收。

③全局/静态存储区

即全局变量和静态变量所在的内存区域。

④常量存储区

即常量所在的内存区域,其内存中的数据一旦初始化就不能被更改。

⑤代码区

顾名思义即为程序代码加载入内存的区域。

28. new/delete与new[ ]/delete[ ]必须配对使用

因为new向堆区动态申请一个对象的空间,delete是动态释放一个对象的内存空间,

而new[ ]是向堆区动态申请对个对象的空间,delete[ ]是动态释放多个对象的内存空间。

实际new [ ]在申请内存的同时还申请了内存保存申请对象的个数,在用delete[ ]时其会去寻找保存的这个对象个数,进行准确释放。

实际new操作基本数据类型对象,因为基本数据类型没有构造和析构函数可以用delete去释放也可以用delete[ ],但是为了规范和增加代码的可读性还是让new与delete配对使用,new[ ]和delete[ ]配对使用。

29. 区分new的三种状态

new实际有三种状态,不要认为自己对new很了解。

①new operator

第一种状态,其不能重载,其永远都会做三件事

//先为对象分配内存
void *memory = operator new (sizeof(对象类型))
//调用构造函数,初始化内存中的对象(当然对于基本数据类型是没有第二步的)
call 构造函数
//返回对象指针
对象类型 * pStr=static_cast<对象类型 *>(momory)

②operator new

第二种状态,第一种状态包含第二种状态(第一步),可以重载。

其会先向堆区寻找空闲内存,如果失败则调用new_hander函数在重复上述操作,直到抛出ball_alloc异常。

③placement new(包含在new头文件中),一般不使用。

30. new内存失败后应该如何处理

c中malloc函数申请内存失败返回一个NULL指针,可以通过检查NULL指针来处理。

c++中new,如果operator new申请内存错误其最终会抛出一个bad_alloc异常,所以这时候应是处理异常而不是检查NULL指针。

检查异常的方法为:

try
{
    int* pStr=new int
}
catch(const bad_alloc& e)
{
    //异常处理
}
当然在一些较老的编译器中并不支持这种,那便用检查NULL的方法。

31. 了解new_handler的所作所为

在使用operator new失败后其不是直接抛出bad_alloc异常的,其会先调用函数new_handler,这个函数进行一些操作

①使更大的内存块有效,通过在程序开始时分配一块大的内存,当调用new_handler时把其释放,这样在次operator new时就会成功了。

②装载其他的new_handler函数

因为如果当前的new_handler函数不能调用更大的内存供operator new使用,而有new_handler函数可以完成,则会调用set_new_handler函数把当前的new_handler函数换成那个函数。

③抛出bad_alloc异常

④直接调用exit( )或abort()函数结束程序。

当然可以对new_handler函数进行定制,

即利用set_new_handler函数装入新的new_handler函数。

32. 利用工具解决内存泄露

在程序的入口加上:

_CrtSerDbgFlag(    _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG)
                            |   _CRTDBG_LEAK_CHECK_DF                   )

在程序运行结束时没有释放的内存会显示出来。

也可以通过其他工具进行解决。

33. 小心翼翼的重载operator new和operator delete

对于自定义数据类型,要想让操作符对其操作必须对其进行重载,而operator new很特殊其不需要重载就可以使用。

但是如果想对其进行重载也可以,不过一般不能将其重载为全局函数因为这样会覆盖掉所有new操作默认的operator new,一般在类中对其进行重载。

以为在调用operator new时还无this指针所以其应该重载为static类型而且规定第一个参数为size_t类型

class    A
{
public:
    static  void* operator new(size_t size)
    static  void operator delete( void *p)
};

注意重载operator new就必须重载operator delete

34. 用智能指针管理通过new创建的对象

利用智能指针类代替传统指针

auto_ptr智能指针,其包含在memory头文件中。

auto_ptr指向一个new对象,在其生命周期结束时对其所指向资源进行释放,不需要显示的使用delete。

使用方法:auto_ptr<类型> pa(new(类型))

也可以建立一个未指向任何对象的auto_ptr

auto_ptr<int> iPtr;
if(iPtr.get()==0)    //不指向任何对象
{
    iPtr.reset(new int(2011))    ;指向一个对象
}

也可以利用一个auto_ptr对两一个auto_ptr赋值

auto_ptr<int> iPtr2(iPtr);

这样的话iptr的资源就移交给了iPer2, iper不能再用。

auto_ptr有很多缺点,其不支持动态配置数组,在赋值时会发生权力转移,其不支持stl容器。

所以在boost中的新的智能指针有5种:scoped_ptr,scoped_array,shared_ptr,shared_array,weak_ptr

其智能指针支持stl容器,支持动态配置数组。

使用其要遵循两条规则

①smart_ptr不同于T*

smart_ptr实际是一个类(智能指针类),而T_就是一个t类型的指针

所以不能用T_赋值给smatt_ptr

不能用NULL对smart_ptr的变量赋值,应该用其成员函数(因为其实一个类对象)

②不要使用临时的share_ptr对象

临时对象在函数做参数时,因为函数参数处理顺序是随机的,如果先处理其他参数,而这个参数恰好发生异常,则动态指针的临时对象new的指针无法返回,就会产生内存泄漏。