第12章 动态内存
静态内存或栈内存。静态内存用来保存局部static对象、类static数据成员以及定义在任何函数之外的变量。栈内存用来保存定义在函数内的非static对象。分配在静态或栈内存中的对象由编译器自动创建和销毁。对于栈对象,仅在其定义的程序块运行时才存在;static对象在使用之前分配,在程序结束时销毁。
除了静态内存和栈内存,每个程序还拥有一个内存池。这部分内存池被称为自由空间或堆。程序用堆来存储动态分配的对象。
12.1 动态内存与智能指针
在C++中,动态内存的管理通过一对运算符来完成的:new在动态内存中为对象分配空间并返回一个指向该对象的指针。delete接受一个动态对象的指针,并销毁该对象,并释放与之关联的内存。
动态内存的使用很容易出问题。有时会忘记释放内存,这种情况容易造成内存泄漏;有时在尚有指针引用内存的情况下我们就释放了它,这种情况就会产生非法使用内存的问题。
为了更容易也更安全地使用动态内存,新地标准库提供了两种智能指针。它们负责自动释放所指向地对象。shared_ptr允许多个指针指向同一个对象,unique_ptr则“独占”所指向地对象。此外标准库还定义了一个名为weak_ptr的伴随库,它是一种弱引用。这三种类型都定义在memory头文件中。
12.1.1 shared_ptr类
类似vector,智能指针也是模板。因此在我们创建一个智能指针时,必须提供额外的信息——指针可以指向的类型。
shared_ptr<string> p1; //shared_ptr,可以指向string
shared_ptr<list<int>> p2; //shared_ptr,可以指向int的list
默认初始化的智能指针都保存一个空指针。智能指针的使用方式与普通指针类似。解引用一个智能指针返回它指向的对象。
//如果p1不为空,检查它是否指向一个空string
if(p1 && p1->empty())
*pt="hi";
**
表1 | shared_ptr和unique_ptr**都支持的类型 |
---|---|
shared_ptr<T> sp | 空智能指针,可以指向类型为T的对象 |
unique_ptr<T> up | |
p | |
*p | |
p->mem | 等价于(*p).mem |
p.get() | 返回p中保存的指针 |
swap(p,q) | 交换p和q中的指针 |
p.swap(q) |
shared_ptr独有的操作
表2 | shared_ptr独有的操作 |
---|---|
make_shared<t>(args) | |
shared_ptr<T>p(q) | p是shared_ptr q的拷贝,此操作增加q中的计数器,q中的指针必须能转换为T* |
p=q | p和q都是shared_ptr,所保存的指针必须能相互转换。此操作会递减p中的引用计数,递增q中的引用计数。若p中的引用计数变为0,则将其管理的内存释放 |
p.use_count() | 返回与p共享对象的智能指针数量。 |
p.unique() | 若p.use_count()为1,返回true;否则返回false |
make_shared函数
//指向一个值为42的shared_ptr
shared_ptr<int> p3 = make_shared<int>(42);
shared_ptr<string> p4 = make_shared<string>(10, '9');
shared_ptr<int> p5 = make_shared<int>();
auto p6 = make_shared<string>();
shared_ptr的拷贝与赋值
auto p = make_shared<int>(42);
auto q(p);
我们可以认为每个shared_ptr中有个关联的计数器,通常其为引用计数。当拷贝一个shared_ptr,计数器都会增加,例如:利用一个shared_ptr初始化另一个shared_ptr,或将它作为参数传递以及作为返回值,它所关联的计数器都会增加。
当我们给shared_ptr赋予一个新值或是shared_ptr被销毁(例如一个局部的shared_ptr离开其作用域),计数器就会递减。一旦一个shared_ptr的计数器变为0,它就会自动释放自己管理的对象。
auto r = make_shared<int>(42);
r = q;
如上面程序,给r赋值,令它指向另一个地址,递增q指向对象的引用计数,递减r原来对象的引用计数,r原来指向的对象已没有引用者,就会自动释放。
shared_ptr自动销毁所管理的对象
当指向一个对象的最后一个shared_ptr被销毁时,shared_ptr类会自动销毁此对象。它是通过析构函数完成销毁工作的。
使用了动态生存期的资源的类
程序使用动态内存主要出于以下三种原因之一:
- 程序不知道自己需要使用多少对象
- 程序不知道所需对象的准确类型
- 程序需要多个对象间共享数据
某些类分配的资源具有与原对象相独立的生存期。例如,假如我们希望定义一个名为Blob的类,保存一组元素。与容器不同,我们希望Blob对象的不同拷贝之间共享相同的元素。即,当我们拷贝一个Blob时,原Blob对象及其拷贝应该引用相同的底层元素。
Blob<string> b1; //空Blob
{
Blob<string> b2={"a","a1","a2"};
b1=b2; //b1与b2共享相同的元素
}//b2被销毁了,但b2中的元素不能销毁
//b1指向最初由b2创建的元素
定义StrBlob类
我们将Blob类实现为一个模板。但是我们不能在一个Blob对象内直接保存vector,因为一个对象的成员在对象销毁时候也会被销毁。比如,假定b1与b2是两个Blob对象,共享相同的vector。如果此vector保存在其中一个Blob中——例如b2中,那么当b2离开作用域时,此vector也会被销毁,所以为了使vector中的元素继续存在,我们将vector保存在动态内存中。实例代码如下:
class StrBlob {
public:
typedef vector<string>::size_type size_type;
StrBlob();
StrBlob(initializer_list <string> i1);
size_type size() const { return data->size(); }
bool empty() const { return data->empty(); }
void push_back();
string& front();
string& back();
private:
shared_ptr<vector<string>> data;
void check(size_type i, const string &msg) const;
};
StrBlob构造函数
StrBlob::StrBlob() :data(make_shared<vector<string>>()) {}
StrBlob::StrBlob(initializer_list<string> i1) : data(make_shared<vector<string>>(i1)) {}
元素访问成员函数
pop_back、front和back操作访问vector中的元素。这些操作在试图访问元素之前必须检查元素是否存在。
void StrBlob::check(size_type i, const string &msg) const
{
if (i >= data->size())
throw out_of_range(msg);
}
string& StrBlob::front()
{
check(0, "front on empty StrBlob");
return data->front();
}
string& StrBlob::back()
{
check(0, "back on empty StrBlob");
return data->back();
}
void StrBlob::pop_back()
{
check(0, "pop_back on empty StrBlob");
data->pop_back();
}
StrBlob的拷贝、赋值和销毁
当我们拷贝、复制或销毁一个StrBlob对象时,它的shared_ptr成员会被拷贝、赋值或销毁。
12.1.2 直接管理内存
C++语言定义了两个运算符来分配和释放内存。运算符new分配内存,delete释放new分配的内存。