C++ 动态内存管理和智能指针:malloc 和 new,free 和 delete,在 C 语言中,我们通常用 malloc 和 free 来动态的管理内存,其中 malloc 用来在堆上开辟空间,而 free 用来释放 malloc 或其他在堆上动态开辟内存的函数所开辟的空间。在 C++ 中,我们用 new/delete;new[]/delete[] 来动态的管理内存,相比于 C 语言中的 malloc 和 free,他们之间有什么差别呢?
首先,它们的调用方式不相同,其中 malloc 和 free 采用下面的方式来调用
- int * p = (int * ) malloc(sizeof(int) * 4); //使用malloc函数开辟4个int类型的空间free(p);p=NULL;//释放上面malloc所开辟的空间
在 C++ 中,new 和 new[]/delete[] 和 delete 主要采用下面的调用方式:
- int * p = new int(4); //开辟一个int类型的对象,初值为4 delete p; //释放上面所开辟的空间 int* p1=new int[4]; //开辟4个int类型的空间,无初值 delete[] p1; //释放上面所开辟的空间
另外,它们之间更重要的差别是,new 和 delete,new[] 和 delete[] 才开辟空间后和释放空间前还分别调用了所开辟类型的构造函数以及析构函数。如下图所示:
另外,C 中的 malloc 开辟空间时如果出现了内存耗尽的情况的话会直接返回 NULL, 而 C++ 中的 new 会抛出异常,不过这里我们也可以声明其不抛出异常使其在内存耗尽的情况下返回 NULL.
- int * p = new int[100000000000]; //出现内存耗尽,抛出异常的方式 int* p1=new(nothrow) int[10000000]; //不抛出异常,出现内存耗尽返回NULL
为什么需要智能指针
在平时写代码时,我们用 new 开辟内存空间时,即使我们记住了需要配对使用,但难免有时候会出现内存泄漏的情况;例如:
- {
- int * p = new int(5);......thorw...;`...delete p;
- }
在上面的这段代码中,即使我们在最后对 new 分配的空间进行了 delete, 如果中间因为某些原因我们抛出了一个异常, 并且我们在异常处理函数中没有对这块内存进行处理, 那么这块内存便会一直停留在操作系统中,从而导致了内存泄漏。
对于这种情况,我们因此引入了智能指针,智能指针采用 RAII(资源分配即初始化机制) 的内存管理方式,即智能指针不是常规指针,只是他的行为类似指针,其实是一种管理内存的对象,在其构造时获取资源,在其对象生命控制期对其访问使用有效,最后在其析构期释放内存。对此不管是否抛出异常,只要离开了当前的作用域,作用域中的智能指针便会自动调用 delete 或 delete[] 进行释放资源。
在 boost 库中主要有下面几种智能指针:
auto_ptr
auto_ptr 采用可以采用 copy 语义来转移指针资源的所有权的同时将原指针置为 NULL,这跟通常理解的 copy 行为是不一致的,而这样的行为要有些场合下不是我们希望看到的,例如 sort 的快排实现中有将元素复制到某个局部临时对象中,但对于 auto_ptr,却将原元素置为 null,这就导致最后的排序结果中可能有大量的 null 从而导致异想不到的后果。
scoped_ptr
scoped_ptr 和 auto_ptr 都表示唯一的所有权持有者,区别在于 scoped_ptr 不允许拷贝构造和赋值的发生,它这这些函数定义为私有成员函数,从而使外部对象无法进行访问。
下面我们来实现一个简单的 scoped_ptr
- templateclass Scoped_ptr {
- public: explicit Scoped_ptr(T * ptr = NULL) : _ptr(ptr) {}
- T & operator * () {
- return * _ptr;
- }
- T * operator - >() { //return &(operatpr*()); //写法1 return _ptr; } ~Scoped_ptr() { delete _ptr; }private: Scoped_ptr(const Scoped_ptr& sp) {} operator=(const Scoped_ptr& sp) {}protected: T* _ptr;};
其中,编译器对 -> 运算符的重载进行了优化
例如:
- struct A {
- int _a;
- int _b;
- int _c;
- };
- Scoped_ptr pa(new A());
- pa - >_a; //(pa->_a):pa.operator->->_a; 两步变一步
shared_ptr
shared_ptr 采用了引用计数的方式来管理内存,如下图:
使用库中的 shared_ptr 指针:
* shared_ptr 不允许隐式的强制类型转换(构造函数和拷贝构造函数前面声明了 explicit)
例如:不能这么使用 shared_ptr
- shared_ptr pa = new int(1); //正确使用方式如下 shared_ptr pa(new int(1));
返回值必须要显示的绑定到 shared_ptr 的指针上
例如:
- shared_ptr ReturnPtr() { //正确方式 shared_ptr tmp(new int(1)); return tmp; //错误方式 return new int(1); }
p.reset() // 若 p 的 shared_ptr 的引用计数为 1,reset 会释放 p 所指向的对象 make_shared // 创建一个 shared_ptr 的对象 使用 shared_ptr 的误区:使用内置指针来访问 shared_ptr 和使用 shared_ptr 来管理内置指针 智能指针类型定义了一个名为 get 的函数,它返回一个内置指针,指向智能指针管理的对象 (注意在使用 get 时不能 delete 指针, 否则后面可能会发生对空指针的解引用的行为) weak_ptr
此时 weak_ptr 就出现了,它采用弱引用的方式,弥补了 shared_ptr 循环引用的问题它的构造和析构不会引起引用记数的增加或减少。没有重载 * 和 -> 但可以使用 lock 获得一个可用的 shared_ptr 对象。
weak_ptr:
- namespace boost {
- template class weak_ptr {
- public: template weak_ptr(const shared_ptr & r);
- weak_ptr(const weak_ptr & r);~weak_ptr();
- T * get() const;
- bool expired() const;
- shared_ptr lock() const;
- };
- }
总结::智能指针能解决 C++ 中的内存泄漏问题,我们应在程序中多去使用智能指针,另外还要注意 shared_ptr 引起的循环引用的问题。
就爱阅读 www.92to.com 网友整理上传, 为您提供最全的知识大全, 期待您的分享,转载请注明出处。
来源: