动态内存与智能指针

来源:互联网 发布:mysql 主键 索引 编辑:程序博客网 时间:2024/05/17 13:10

程序中内存分配有以下五种:

  • 栈区
    存放局部变量、函数参数等,由编译器自动分配,变量离开作用域后自动收回内存,可分配的内存空间较小。
void func(int _a);int main(){    int a = 5;       //局部变量a存放在栈区    func(a);        //参数值存放在栈区}
  • 堆区
    由程序员手动分配与回收,分配内存后生存期直到手动收回内存或程序终止,程序终止后由OS收回内存。可分配的内存空间大。
int *p = new int(5);   //new分配的动态内存在堆区                       //指针变量p本身存放在栈区
  • 全局区(静态区)
    存放全局变量、静态变量,由编译器自动分配,生存期从定义开始直到程序结束。
int a = 10;       //全局变量a存放在全局区int main(){    static int b = 5;   //局部静态变量b存放在静态区}
  • 文字常量区
    字符串常量存放在这里,程序结束后由系统释放。
char s[] = "abac";   //字符串常量"abac"存放在文字常量区                     //字符数组存放在栈区
  • 程序代码区
    存放函数体的二进制代码

智能指针

其中堆区的内存是程序员手动分配的,在学习智能指针之前,我们用裸指针管理这块内存,分配的内存使用完毕后必须手动的将它释放掉,否则会导致内存泄漏;若果有两个指针指向同一块内存,在一个指针将这块内存释放掉后,另一个指针就成了空悬指针,解引用空悬指针会造成不可知的错误,过去使用裸指针只能由程序员来尽量避免这两个错误。

智能指针的设计就是为了解决这样的问题
智能指针与裸指针的最大不同就是智能指针管理它所指内存的生命周期,当一块内存不再被需要后,智能指针就会自动的释放它。

智能指针分为 shared_ptr(共享指针)unique_ptr(独占指针)

  • 共享指针
    是指由几个关联的共享指针共享一块内存,用use_count(引用计数)来表示有几个共享指针使用这块内存,只有当所有共享指针都不指向这块内存后,引用计数变0,这块内存才会被自动释放。

  • 独占指针
    是指由一个指针独占一块内存,当独占指针不再指向这块内存时,自动将其释放。注意独占指针有个方法(u.release())可以放弃对内存的控制权,并返回指向这块内存的指针(供其他指针使用),但并不释放内存。

  • 弱指针
    同时标准库还定义了弱指针(weak_ptr),之所以称作弱指针是因为它并不控制所指内存的生存期,这一点与裸指针十分相似,但弱指针上定义了w.use_count、w.lock()等方法让我们在使用弱指针之前就知道所指内存有没有被释放,只有在内存没有被释放的时候我们才使用它,避免了裸指针的空悬指针错误。

弱指针的一个重要使用场景就是解决共享指针循环引用造成的内存泄漏。
考虑下面的一段程序

class A{public:    shared_ptr<A> pre;    shared_ptr<A> next;    ~A()    {        cout << "Destructed." << endl;    }};int main(){    //一个作用域    {        shared_ptr<A> sp_a1(new A),sp_a2(new A);        sp_a1->next = sp_a2;        sp_a2->pre = sp_a1;    }    //我们期望在离开这个作用域之后,sp_a1和sp_a2会释放它们控制的内存    system("pause");    return 0;}//在离开作用域后,程序没有没有输出析构函数中的Destructed//原因是这两块内存上的共享指针构成了循环引用

共享指针循环引用

这几个指针间构成了一个死锁,这两块内存直到程序结束前都不会被释放

使用弱指针就可以解决这样的问题

class A{public:    weak_ptr<A> pre;    //弱指针不具有对象的生命周期控制权    weak_ptr<A> next;   //避免形成死锁    ~A()    {        cout << "Destructed." << endl;    }};

关于共享指针与独占指针更详细些的用法及陷阱写在这里
shared_ptr使用及陷阱
unique_ptr使用及陷阱


allocator类

在用new分配内存的时候发生了三件事:

  1. 申请一块内存
  2. 构造内存上的对象
  3. 返回指向这块内存的指针

同样在用delete时也会:

  1. 执行内存上对象的析构函数
  2. 释放这块内存

allocator类的作用就是将 分配/回收 内存的操作与 构造/析构 对象的操作分开来。

原创粉丝点击