内存类型分类
- 静态内存:静态内存用来保存局部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分配内存时的大小一样。