内存类型分类
- 静态内存:静态内存用来保存局部static对象,类static数据成员,以及定义在任何函数之外的变量。
- 栈内存:用来保存在函数内的非static对象,由编译器进行自动创建和销毁。
- 堆内存:又称自由空间。是我们在程序运行时分配的对象。动态对象的生存期由程序来控制。所以需要我们显式的销毁。
直接管理内存 -new
,delete
- 如果
new
无法分配所要求的内存空间,它会抛出一个类型为bad_alloc
的异常。 new
的作用是分配所需内存,返回指向该对象的指针delete
的作用是销毁给定指针指向的对象,释放对应内存。new
是可以分配const
对象的。- 动态内存不同于之前的
shared_str
,他不会自动释放,所以它的生存期一直到被显式释放之前。但是在很多情况下,我们都容易忘记释放内存,导致内存泄露。
直接管理内存的劣势:
忘记
delete
内存。使用已经释放过的对象。
同一块内存释放两次。
特别是我们有多个指针指向同一块内存,当其中一个指针
delete
后,其他指针指向的内存也没有了,此时所有指针无效了。但是我们容易忘记该事实。
智能指针
智能指针是c++11新标准颁布的,用来管理动态内存。智能指针与以前的new,delete
区别主要在于他可以自动释放所指向的对象。主要有两类智能指针:shared_ptr
和unique_str
。shared_ptr
允许多个指针指向同一个对象,unique_str
独占所指向的对象。
shared_ptr
shared_ptr
也是一种模板,定义时我们需要提供指针可以指向的类型:
1 | shared_ptr<string> p1; //可以指向string类型 |
shared_ptr
分配内存
shared_ptr
使用make_shared
标准库函数来分配内存。
1 | // shared_ptr that points to an int with value 42 |
注意一点,调用make_shared<string>时
传递的参数必须与string
的某个构造函数相匹配。如果不传递任何参数,对象会自己进行初始化。
我们也可以使用c++11新标准的auto
关键字进行定义
1 | auto p6 = make_shared<vector<string>>(); |
shared_ptr
拷贝与赋值
当进行拷贝与赋值操作时,每个shared_ptr
都会记录有多少个其他的shared_ptr
指向相同的对象。
每一个shared_ptr
都有一个关联的计数器。当我们拷贝一个shared_str
时,计数器就会递增。
基本上一个例子我们就能明白。
1 | auto r = make_shared<int>(42); // r指向的int只有一个引用者 |
shared_str
自动销毁所指对象
shared_str
是通过一个特殊的成员函数-析构函数来完成销毁工作的。如果当引用计数为0时,它的析构函数就会自动销毁对象,释放内存。
shared_str
自定义删除器
如果我们希望被销毁时,对指针不是单纯的进行delete
操作,我们可以定义一个自己的删除器函数传给智能指针。
1 | void end_connection(connection *p) { disconnect(*p); } |
我们可以看到上面的例子,当p被销毁时,我们会调用end_connection
,来确保链接被关闭。这样即使在代码运行中发生异常,由于智能指针的特质,连接也一定会被关闭。
unique_str
unique_str
与shared_str
不同的是,某个时刻只能有一个unique_str
指向一个给定对象。当unique_str
被销毁时,它所指向的对象也会被销毁。
unique_ptr
分配内存
unique_str
没有make_shared
这样的标准库函数来返回动态指针。我们只能使用new
进行直接初始化。
1 | unique_ptr<double> p1; |
unique_ptr
拷贝与赋值
因为unique_str
绑定了他指向的对象,所以她不能进行普通的拷贝或者赋值。我们只能调用release
或者reset
来转移指针的所有权。
1 | // transfers ownership from p1 (which points to the string Stegosaurus) to p2 |
我们可以发现,利用release
将切断unique_ptr
和他所绑定对象的关系,返回的值可以用来初始化另一个unique_ptr
。这样就达到了所有权转移的目的。
unique_str
自定义删除器
unique_str
自定义删除器和上面shared_str
不同,我们必须在尖括号中unique_str
指向类型后提供删除器类型。重写上面shared_str
自定义删除器的例子:
1 | void f(destination &d ) |
在这个例子中,decltype
返回的是一个函数类型,所以我们必须添加一个*
来指出我们该类型的一个指针。
unique_str
管理动态数组
unique_str
初始化动态数组的时候,我们要在对象类型后加一对空方括号。
1 | // up 指向一个包含10个非初始化int的数组 |
当一个unique_str
指向一个数组时,我们可以使用下标运算符来访问数组中的元素。
1 | for (size_t i = 0; i != 10; ++i) |
allocator
类
new
操作将内存分配和对象构造结合在一起,delete
将对象析构和内存释放组合在一起。但是有的时候,我们希望将内存分配和对象构造分离。我们可以先分配大块内存,然后再真正需要时才执行对象创建操作。
allocator
分配内存
allocator
是一个模板,为了定义一个对象,我们要指明这个allocator
可以分配的对象类型。当一个allocator
对象分配内存时,它会根据对象类型来确定恰当的内存大小。但是这些内存是未构造的,所以我们需要的时候,要构造相应的对象。
1 | allocator<string> alloc; |
allocator
构造对象
我们可以使用construct
来在未分配的内存初始化构造的对象。
1 | auto q = p; // q 指向最后构造的元素之后的位置 |
allocator
销毁对象
当我们用完对象后,我们需要销毁这个对象,对指向的对象执行析构函数。
1 | while (q != p) |
我们需要用一个while循环,来不停的销毁对象,并且对q进行递减操作。这样能够保证所有构造元素都被销毁。销毁后,我们可以重新使用这部分内存来保存其他的string
。
allocator
销毁内存
1 | alloc.deallocate(p, n); |
我们传递给该函数的指针不能为空,必须指向由allocate
分配的内存。而且传递给deallocate
的大小参数必须和allocate
分配内存时的大小一样。