智能指针
内存分类
- 静态内存
- 用来保存局部static的对象。
- 栈内存
- 保存函数内部的非static对象。
- 堆内存(自由空间)
- 用来动态分配内存。 (new | delete)
智能指针
- 内存泄漏: 使用new分配内存之后,没有delete释放内存。指向这片内存的指针被销毁后,这片内存无法再被操控。造成了内存浪费,即内存泄漏。
- 头文件: memory
分类
shared_ptr
- 允许多个指针指向同一个对象。
unique_ptr
- 独占所指向的对象。
weak_ptr
- 伴随类, 是一种弱引用,指向share_ptr所管理的对象。
引用计数的实现
除了初始化对象外,每个构造函数(拷贝构造函数除外)还要创建一个引用计数,用来记录与多少对象与正在创建的对象共享类型。当我们创建一个对象时,只有一个对象共享状态,因此将引用计数初始化为1
拷贝构造函数不分配新的计数器,而是拷贝构造函数给定的对象的数据成员,包括计数器。拷贝构造函数递增共享的计数器,指出给定的对象的状态又被一个新用户所共享
析构函数递减计数器,指出共享状态的用户少了一个。如果计数器变为0,则析构函数释放状态。
拷贝赋值运算符递增右侧运算对象的计数器,递减左侧运算对象的计数器。如果左侧运算对象的计数器变为0,意味着他的共享状态没有用户,拷贝赋值运算符就必须销毁对象。
计数器保存在动态内存中
#include <string> class HasPtr { public: // constructor allocates a new string and a new counter, which it sets to 1 HasPtr(const std::string &s = new std::string()) : ps(new std::string(s)), i(0), use(new std::size_t (1)) {} // copy constructor copies all three data members and increments the counter HasPtr(const HasPtr &p) : ps(p.ps), i(p.i), use(p.use) { ++ *use; } HasPtr& operator= (const HasPtr& rhs) { // increment the use count of the right-hand operand ++*rhs.use; if(--*use == 0) { // then decrement this object’s counter delete ps; // if no other users delete use; // free this object’s allocated members } ps = rhs.ps; // copy data from rhs into this object i = rhs.i; use = rhs.use; return *this; // return this object } ~HasPtr() { if(--*use == 0) { // if the reference count goes to 0 delete ps; // delete the string delete use; // and the counter } } private: std::string *ps; int i; // member to keep track of how many objects share *ps size_t *use; // --> reference counter };
new
delete
- 运算符new分配内存,返回一个指向内存的指针。、
- 对于内置类型 ,进行值初始化
int *pi=new int ();
很有必要,可以得到一个良好定义的值。 - new也可以用来分配const对象,必须初始化。
const int *pi= new const int(1024);
- new返回的指针是指向const的指针(底层const)
- 内存耗尽: (nothrow)
int *pi=new int ;
//分配失败,抛出 std::bac_alloc 异常。int *pi= new (nothrow) int ;
//就算分配失败,也不会抛出异常。
- 使用delete释放动态内存。
- 销毁给定指针指向的对象; 释放对应的内存。
- 释放一块非new分配的地址,或者相同地址释放多次都是未定义的行为。
- const对象的值虽然不能修改,但他可以被delete释放
- 【空悬指针dangling pointer】
- delete一个指针后,指针仍然保存着被释放的那片内存的地址。
shared_ptr
- 自动销毁所管理的对象,并且释放相关联的内存。
- 构造函数是explicit的, 拒绝隐式转换。不能使用拷贝复制……
- 如果返回值是shared_ptr, 必须把一个临时的share_ptr类型对象绑定到要返回的指针上。
- 智能指针一般用来指向动态内存。 如果要指向其他资源,必须定义自己的释放操作。 (其他资源使用delete不一定释放得了。)
- 不要混合使用普通指针和智能指针(只允许share_ptr之间传值,避免指向同一块内存的两个智能指针相互独立统计引用次数),也不要用get初始化另一个智能指针或为智能指针赋值。
- 如果将一个智能指针和一个内置指针都指向了同一块内存区域,则内存管理的责任就应该由智能指针负责,不应该再使用内置指针(那片内存可能已经被智能指针释放了,内置指针变成了空悬指针)。
- reset成员可以令一个share_ptr指向别的动态地址。
p.reset();
p.reset(q);
p.reset(q,d);
- 若p是唯一指向其对象的shared_ptr ,reset会释放此对象。
- 若传递了q, 则令p指向q ,上述情况依然执行。
- 若还传递了d,则调用d而不是delete来”释放内存”。
- 发生异常时
- 对于普通指针管理的动态内存,如果没有执行delete ,就算程序退出(1.程序执行完毕; 2.发生异常)了,也会发生内存泄漏。
- 对于动态指针管理的内存,如果程序退出(主要指发生异常),仍旧会递减引用次数以及释放动态内存。
- 在一些其他资源(非动态内存)上,也可以使用智能指针,但要声明自己定义的删除器。
- 用于没有析构函数来清理对象使用的资源的类。
shared_ptr<connection> p(&c, end_connection);
- c是智能指针p所指向的地址。 end_connection是一个删除器(是一个函数指针),相当于delete。
- 在地址c的引用计数归0后,调用end_connection。
unique_ptr
只能有一个unique_ptr指向一个内存区域。
初始化unique_ptr必须采用直接初始化形式,不支持普通的拷贝或赋值操作。但可以拷贝或赋值一个即将销毁的unique_ptr
unique_ptr:不支持普通的拷贝或赋值,但可拷贝或赋值一个即将销毁的unique_ptr,如函数返回值;调用release或reset将指针的所有权从一个非const的unique_ptr转移给另一个unique_ptr;调用reset()或用nullptr赋值可以释放对象,release只会切断管理关系
unique_ptr的删除器:管理方式与share_ptr不同,删除器会影响到类型及构造过程;重载删除器必须在尖括号里指明删除器类型,并在参数列表里指定一个可调用对象(删除器)
weak_ptr
- weak_ptr:是一种弱引用,指向shared_ptr的对象,但不改变引用计数;调用lock返回指向的shared_ptr对象,当对象计数器为0时返回空shared_ptr
动态数组
- 分配动态数组
- 使用new(int [size_t]);
- 返回一个类型指针,指向第一个元素
- 初始化时,可以提供一个元素初始化器的花括号列表。但是因为不能在括号中给出初始化器,所以不能用auto分配数组。
- 可以分配一个空数组,返回类似尾后指针的指针。
- 释放动态数组
- 要使用 delete[] , 最后不能没有方括号,否则会在没有警告的情况下行为异常。
- 动态数组的管理
- 可以使用unique_ptr管理动态数组(unique_ptr
allocator类
- 可以将内存分配和对象构造分离。分配的内存是原始的、未构造的。