前言
对象有生命周期,C++对象的生命周期与存储位置是息息相关的。
C++对象按照创建位置可以分为全局对象、局部对象、局部静态对象。他们的生命周期是固定的,由编译器创建和销毁。另外,C++支持动态分配对象,动态对象的分配和释放需要程序显示的操作。
C++内存分为静态存储区、栈内存、堆内存三种。
- 静态内存:保存局部static对象、类static数据成员、全局变量
- 栈内存:保存函数内定义的非static对象
- 堆内存:存储动态分配的对象
静态内存随程序启动直到程序结束为止,栈内存随方法调用直到方法返回结束,堆内存随程序分配到程序释放为止(最后一定会被系统收回)。
动态内存
C++最常用的内存管理是通过 new
/ delete
来完成的。new,在动态内存为对象分配空间并返回一个该对象的指针,我们可以选择对对象进行初始化。delete,接受一个动态对象的指针,销毁对象,并释放内存。注意,这里每个操作都有两个子操作
new
: 分配空间,初始化delete
: 销毁对象,释放空间
新的标准库提供了分别进行子操作的能力,能够提高性能,后面会介绍。
注意:
默认情况下,new 动态分配的对象是默认初始化的,即内置对象值是未定义的,类类型对象使用默认构造函数注意:
内置指针管理的动态内存直到被显示释放之前会一直存在
//new
int *pi = new int; // pi 指向动态分配的,未初始化的无名对象
string *ps = new string; // 初始化为空串
int *pi = new int(1024); // 直接初始化
string *ps = new string(10, '9'); // "9999999999"
vector<int> *pv = new vector<int>{0, 1, 2, 3, 4};
int *pi = new int(); // 值初始化为0
//delete
delete pi; // 传递给delete的指针必须是指向动态分配的内存或者空指针
delete ps;
delete pv;
const int *pci = new const int(1024);
delete pci; // const对象值不能改变,但是本身可以被销毁
//数组
int *pia = new int[get_size()]; // pia 指向第一个int
int *pia2 = new int[10](); // 初始化为0
int *pia3 = new int[10]{0, 1} // 0, 1, 0, 0, 0, 0...
char arr[0]; // 错误:不能定义长度为0的数组
char *cp = new char[0]; // 正确:但cp不能解引用
delete [] pa;
容易出错的三点:
- 忘记delete
- 使用已经释放的对象(一定要记得释放之后设置为空指针)
- 同一块内存释放两次
智能指针
前面我们提到使用内置的new
/ delete
操作动态内存,会导致一些问题,这些问题主要是程序员的错误导致的。另外,动态分配内存会绑定分配空间和初始化的操作。特定的场景下性能不佳。因此,C++新的标准库为我们提供了智能指针。
需要提一下的是,有一些其他的著名的C++库也提供智能指针,比如boost library
,这里就不对他们进行分析了。工程上遇到了问题再去了解也不迟。
C++primer提到的标准库两种智能指针,shared_ptr
和 unique_ptr
, 还有一个伴随类weak_ptr
。shared_ptr
允许多个指针指向同一对象,unique_ptr
则“独占”所指向的对象。三种类型都定义在memory
头文件里
- shared_ptr 类
#include <memory>
shared_ptr<string> p1; // 空智能指针
if (p1) {} // p指向一个对象,则为true
*p; // 解引用
p.get(); // 返回p中保存的指针
swap(p, q); // 交换指针
p.swap(q);
auto p = make_shared<T>(arg); // 返回一个shared_ptr,指向用args初始化的对象
auto q = shared_ptr(p); // q是p的拷贝;增加p的计数器
p = q; // 递减p的计数器,增加q的计数器,若计数器为0,则释放内存
p.unique(); // 是否只有一个引用
p.use_count(); // 返回共享对象的智能指针数量,可能很慢
- unique_ptr 类
#include <memory>
unique_ptr<string> p1;
unique_ptr<string> p2(new int(42));
unique_ptr<string> p3(p2); // 错误,unique_str独占对象,因此没有拷贝或者复制操作
unique_ptr<string> p3 = p2; // 错误
u = nullptr; // 释放对象,将u置空
u.release(); // u放弃对指针的控制权,返回指针,并将u置空
u.reset(); // 释放对象,将u置空
u.reset(q); // q是内置指针
p2.reset(p3.release()); // 将p3 转移给p2
- allocator 类
#include <memory>
allocator<string> alloc; // 可以分配string的allocator对象
auto const p = alloc.allocate(n); // 分配n个未初始化的string
auto q = p; // q指向未构造空间的首地址
alloc.construct(q ++);
alloc.construct(q ++, 10, 'c');
alloc.construct(q ++, "hi");
cout << *p;
while(q != p) // 释放空间,首先destroy,然后deallocate
alloc.destory(-- q);
alloc.deallocate(p, n);
auto p = alloc.allocate(vi.size() * 2); // 拷贝一个vector数组为两倍,并在后面填充42
auto q = uninitialized_copy(vi.begin(), vi.end(), p);
uninitialized_fill_n(q, vi.size(), 42);
智能指针使得动态内存管理更加安全,应该尽可能的使用智能指针,同时也可以自己设计智能指针模拟指针的行为。主要在包装类中设置一个引用计数器,在析构函数中递减引用计数器,当引用计数器为0则释放内存。拷贝构造时,增加引用计数。