C++内存管理

摘要:本文讨论对比了手动、标准库auto_ptr自动指针以及Boost智能指针三种方式管理内存的优缺点,在立足应用的基础上,结合自己的实际开发经验,总结出实际开发过程中管理C++内存的方法,有助于C++初学者明晰开发过程中应该如何管理好内存。

 

“C++内存分配的方式有哪些?

内存泄漏---导致程序崩溃的恶梦,手动释放内存到底好不好?

       C++标准中auto_ptr为我们在管理内存上提供了哪些帮助,还有更好的实现方式吗?

       Boost智能指针是什么,到底该怎么用,它和auto_ptr的使用有什么区别?

C++内存管理的最佳实践方式是什么,怎样才能管理好我们的内存,保证万无一失?”

下面我将围绕以上的几个问题展开讨论,寻找C++内存管理的最佳实践方式,C++内存管理是每一个C++程序员必须认真对待和掌握的,在这里,一是想和大家分享自己的学习成果和开发经验,二是想借此文总结一下C++内存管理的方法,明晰开发过程中应该如何管理好内存。

C++内存分配方式

在进入C++内存管理之前,首先让我们来了解一下C++内存分配的几种方式:

(1)从静态存储区域分配。内存在程序编译的时候就已经分配好了,这块内存在程序的整个运行期间都存在。例如全局变量,static变量,虚函数表等。

(2)在栈上创建。在执行函数时,函数内局部变量的存储单元都是在栈上创建的,函数执行结束时这些存储单元将自动被释放。对于局部类对象类型,当其离开作用域时,会自动调用其析构函数来释放其内存,这个特点是RAII(资源获取就是初始化)技术来管理动态内存的依据,在局部对象的析构函数中调用delete或delete[]来释放动态内存,以达到自动释放的效果。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

(3)从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多,很容易引起内存泄漏、溢出等问题的发生。

 

内存泄漏---导致程序崩溃的恶梦,手动释放内存到底好不好?

一般我们常说的内存泄漏是指堆内存的泄漏。堆内存是指程序从堆中分配的,大小任意的(内存块的大小可以在程序运行期决定),使用完后必须显示释放的内存。在C++中,当你使用operator new在堆中分配了内存过后,只有调用对应的operator delete或operator delete[],程序才能够调用对象的析构方法来释放空间,否则该空间将永远被占用,我们就说这块内存泄漏了。

内存泄漏这个问题是所有C++初学者的恶梦,它甚至成为其他语言开发人员跨入为C++程序开发的一道门槛。早些年的C++在管理内存上确实有诸多不变,程序员都是采用手动释放内存的方式来管理内存,我们也可以在早期的C++代码中看到很多诸如使用try…catch来四处捕获异常释放资源的代码,这种方式可以说是既复杂又容易出错,稍微一不留神就会出现内存泄漏,而且也让代码变得臃肿。

误区提醒:内存泄漏是在进程结束之前发生的问题,进程结束之后,所有的资源将会由操作系统自动回收。

 

auto_ptr---自动指针

C++标准委员会的大牛们意识到手动释放内存的弊端,不断改进管理内存的方式,C++98标准中引入了“自动指针”std::auto_ptr,它部分的解决了获取资源自动释放的问题。

使用方法如下:

         #include         //使用auto_ptr需要引入memory库

    auto_ptr<T> p1(new T());//p1在离开其作用域时,自动释放其指向的堆内存

为什么说auto_ptr只是部分的解决了自动释放资源的问题呢?因为auto_ptr有着自身的一些不完美:一是,它只能管理单一资源,无法对数组进行管理,因为其自动释放资源的时候,调用的是operator delete方法,而不是operator delete[];二是,C++标准规定其不能作为容器的元素;三是,它没有引入对资源智能计数的功能,对资源的所有权只能转移,不能共享。

 

Boost智能指针的使用

Boost简介:Boost是一个功能强大、构造精巧、跨平台、开源并且完全免费的C++程序集,它由C++标准委员会部分成员所设立的Boost社区开发并维护,有着“C++’准’标准库”的美誉。

本文主要对最常用的shared_ptr的使用作介绍,使用它几乎可以替换所有需要使用指针的地方,其他的指针相对来说不太常用,都有一定的局限性,所以在此不作介绍,如果大家有兴趣可以去查看官方的Boost开发文档。

share_ptr的使用方法(由于篇幅所限,在此只讲解基本的使用方法,其他方法的使用请参看Boost开发文档):

#include //引入boost/shared_ptr.hpp库

boost::shared_ptr<T> p1(new T());//p1对T拥有控制权,注:T是一个模版类

boost::shared_ptr<T> p2=p1;//此时p1和p2共享T的控制权,引用计数加1

p1.reset();//p1释放控制权,引用计数减1

p2.reset();//p2释放控制权,引用计数变为0,自动释放T对象资源,注:当指针离开作用域时,引用计数也会减1

//定制删除器的用法

void delete_DIY(){

    //…输入你要执行的自定义释放代码

}

boost::shared_ptr<T> p_d(new T(),delete_DIY);//自定义删除器,当T对象释放的时候,执行自定义的delete_DIY方法释放资源

share_ptr的优点:它是实现了引用计数型的智能指针,可以被自由地拷贝和赋值,在任意的地方共享它,当没有代码使用(引用计数为0)它时才删除被包装的动态分配的对象;同时,它也可以安全的放到标准容器中,并弥补了auto_ptr因为转义语义而不能把指针作为STL容器元素的缺陷,它是目前为止引用计数型智能指针中最好的实现。

当然,share_ptr也有自身的缺点:

一是,它虽然能够对容器进行包装,但是仍然不能用于管理数组,管理数组可以用shared_array智能指针,不过由于数组可以用容器表示,多数情况下可以用boost::shared_ptr< vector<T> >和vector< boost::shared_ptr<T> >代替,这两个方案具有更好的安全性和灵活性,所以这个缺点几乎可以忽略。

二是,它作出了很多努力来管理资源,所以其效率上肯定比原始指针和auto_ptr要低一些,当然开销并不是我们不使用shared_ptr的理由,永远不要进行不成熟的优化,直到性能分析器告诉你不得不做,这是Hurb提出的明智的建议。

三是,使用shared_ptr,必须遵守以下3条规则:

1.    避免对shared_ptr所管理的对象的直接内存管理操作,以免造成该对象的重复释放。

2.    shared_ptr并不能对循环引用的对象内存自动管理(这点是其它各种引用计数管理内存方式的通病)。

3.    不要构造一个临时的shared_ptr作为函数的参数。

 

C++内存管理的最佳实践

能用局部对象表示的东西,尽量不要operator new。因为局部对象是在栈空间分配内存的,对象离开作用域以后,会自动调用其析构函数释放内存,而new是在堆上分配空间的,即使离开作用域也不会释放空间,必须调用operator delete或者operator delete[]来释放内存。

使用shared_ptr几乎可以完全胜任内存管理的工作。随着C++ 2011正式成为新的C++标准,C++在库方面的使用已经相当频繁了,人们更加专注的是产品完成的效率和质量,小小的性能损失往往是可以被接受的。所以,目前为止,shared_ptr理当成为C++内存管理的首选,如果在不得不优化性能的时候,我们可以使用auto_ptr,最不得已的情况下,才自己手动管理内存。

路漫漫其修远兮!这篇文章主要是站在应用的角度来讲解如何管理C++内存,如果大家需要了解C++对象的内存布局,大家可以去看看C++的源码以及《深度探索 C++对象模型》,慢慢深入学习下去。

 

 

参考资料:

【1】 《Effective C++ 改善程序与设计的55个具体做法(第三版)》,[美]Scott Meyers

【2】 《Boost程序库完全开发指南》,罗剑锋

【3】 《More Exceptional c++》,[美]Herb Sutter

 

你可能感兴趣的:(C++,c,delete,c,raii,存储,程序开发)