静态内存用于保存局部static
对象,类中的static
数据成员以及定义在任何函数之外的变量
栈内存用于保存定义在函数之内的非static
对象
分配在这两者中的对象由编译器自动创建和销毁
除此之外 每个程序还有一个内存池 被称作自由空间或堆
用于储存动态分配的对象 这些对象是程序运行时分配的对象
由程序来控制生存期 即必须被代码显式地销毁
C++中动态内存管理是通过一对运算符new
和delete
来完成的
智能指针
为了更安全更容易地使用动态内存 新标准还提供了两种智能指针: shared_ptr
和 unique_ptr
它们负责自动释放所指向的对象
这两者的区别在于管理底层指针的方式:
shared_ptr
允许多个指针指向同一对象- 而
unique_ptr
独占所指向的对象
这两者都是模板类 定义在头文件memory
中
默认初始化的智能指针保存着一个空指针
shared_ptr
最安全的分配和使用动态内存的方法是调用make_shared()
shared_ptr<int> p = make_shared<int>(42);
当对shared_ptr
进行拷贝和赋值时 每个shared_ptr
都会记录当前有多少个其他shared_ptr
指向相同的对象
可以认为每个shared_ptr
都有一个关联计数器 称之为引用计数
当进行拷贝初始化或将其作为参数传递给另一个函数以及作为函数返回值时 引用计数+1
当对其赋予新值或是其被销毁时(如局部shared_ptr
离开作用域) 引用计数-1
一旦shared_ptr
的引用计数为0 则自动释放它所管理的对象
shared_ptr
是通过析构函数来完成销毁工作的
使用动态内存的一个常见目的是允许多个对象共享相同状态
在自由空间分配的内存是无名的 因此new
无法为其分配的对象命名 而是返回一个指向该对象的指针
通过new
可以分配动态数组 int *p = new int[12];
但事实上动态数组并不是数组类型 而是其对应元素类型的指针
故不能使用一些数组操作 比如对其使用begin()
和end()
函数 也不能使用for
范围语句来处理
new
分配的对象 不管是单个的还是动态数组 都是默认初始化的
因此可能会出现未定义的情况 最好是定义时进行初始化
如果初始化器数目小于元素数目 剩余元素将进行默认值初始化 **
若大于 则抛出异常并且不会分配任何内存**
int *pi = new int(1024); //pi指向的对象的值为1024
string *ps = new string(3,'a'); // *ps为"aaa"
vector<int> *pv = new vector<int>{0,1,2,3};
int *pia = new int[3]{0,1}; //列表初始化动态数组 前两个元素为对应初始化器元素 第三个元素值初始化为0
虽然不能定义长度为0的数组 但可以定义长度为0的动态数组
因为动态数组本质上是个指针而不是数组类型 长度为0的动态数组返回合法的非空指针 类似于尾后指针 不可对其解引用
相对应的 与释放单个对象不同 释放动态数组时需要在指针前加一个方括号
delete p;//删除单个对象
delete [] pa; //删除动态数组
删除动态数组时 数组中的元素按逆序销毁
即最后一个先被销毁 然后是倒数第二个 以此类推
可以使用直接初始化的方式来利用new
来初始化智能指针
shared_ptr<int> p1(new int(1024));
//注意 不能使用赋值初始化 因为指针参数的构造函数是explicit的 不接受隐式转换
shared_ptr<int> p1=new int(1024) //这种方式是错的 不能使用赋值初始化
使用内置指针初始化智能指针时 要么内置指针指向的是动态内存 要么就必须自己为其提供定义delete
操作
shared_ptr<T> p(q, del) //del为自定义的释放操作
使用delete
释放内存时 释放非自由空间的内存或是多次释放同一内存 都是未定义的行为
不要混用普通指针和智能指针
因为将一个智能指针绑定到一个普通指针时 就将内存的管理责任也移交了
一旦这么做了 就不应该再使用内置指针来访问内存了 因为可能会被智能指针释放
unique_ptr
可以使用unique_ptr
来管理new
分配的动态数组
unique_ptr<int []> up(new int[10]);
unique_ptr
定义了下标运算符 可以直接通过up[i]
来访问动态数组中的元素
但shared_ptr
不直接支持管理动态数组
如果要使用shared_ptr
管理动态数组 必须提供自己定义的删除器
shared_ptr<int> sp(new int[10], [](int *p){delete []p});
shared_ptr
不支持下标 并且智能指针类型不支持算术运算
因此必须使用get
来返回一个内置指针 通过这个内置指针访问元素
for(int i = 0; i != 10; ++i) {
*(sp.get( ) + i) = i;
}
使用.get()
函数可以从智能指针返回一个普通指针提供给那些不能使用智能指针的代码
但永远不要使用get
初始化另一个智能指针或为另一个智能指针赋值
通过.reset()
函数可以改变shared_ptr
的指向
.reset()
将指针置空
.reset(q)
将指针指向q
.reset(q, d)
利用d
而非delete
释放q
因为如果使用智能指针管理的资源不是new分配的动态内存 要传递一个删除函数
不要使用相同的内置指针初始化或reset
多个智能指针
与shared_ptr
不同 没有类似于make_shared
这种库函数
当定义一个unique_ptr
时 需要将其绑定到一个new
返回的指针上
并且必须使用直接初始化方式 不能赋值初始化
不能直接拷贝或者赋值unique_ptr
但可以通过调用.release()
或.reset()
将指针的所有权从一个非const
的unique_ptr
转移给另一个
.release()
返回当前保存指针并将其置空 可以切断unique_ptr
与当前管理对象的联系
但不会释放内存 如果我们不用另一个智能指针来保存.release()
返回的指针 就必须通过程序来负责资源的释放
与shared_ptr
不同 向unique_ptr
传递删除器会影响到unique_ptr
的类型
所以 与重载关联容器的比较操作类似 必须在类型定义时 即unique_ptr后的尖括号中 在类型之后额外指定删除器类型
在创建和reset
一个这种unique_ptr
对象时 都必须提供一个指定类型的可调用对象用作删除器
unique_ptr<int, decltype(end_connection)*> p(&c, end_connection);
weak_ptr
weak_ptr
是一种不控制所指向对象生存期的智能指针
它指向于一个shared_ptr
管理的对象
将一个weak_ptr
绑定到一个shared_ptr
上不会改变shared_ptr
的引用计数
即使有weak_ptr指向对象 也不影响引用计数归0后shared_ptr
释放内存 所以是一种"弱"共享
由于对象可能不存在 不能使用weak_ptr
直接访问对象
而必须使用.lock()
函数 此函数检测weak_ptr指向的对象是否存在 存在则返回指向该对象的shared_ptr
否则返回一个空的shared_ptr
weak_ptr
可以用定义核查指针 在使用前检测某个对象是否存在
allocator
通过标准库的allocator
类 可以将内存分配和对象构造分离开来
它分配的是原始的 未构造的内存 它是一个模板类
allocator<string> alloc;
auto const p = alloc.allocate(n);
为了使用allocator
分配的内存 必须通过.construct()
为allocator
分配的内存构造对象
auto q = p; //q指向最后一个元素之后的位置
alloc.construct(q++); //构造空串
Alloc.construct(q++, 3, 'c'); // 构造了一个 ccc 的字符串
Alloc.construct(q++, "hi"); // 构造了一个 hi 的字符串
使用结束后 通过.destory()
可以销毁单个对象
销毁元素以后 可以通过.deallocate(p, n)
释放从p
开始的n
块内存