首页笔记:
1.变量的生命周期:
变量类型 | 啥时候放进内存 | 啥时候销毁 |
---|---|---|
全局变量 | 程序运行开始 | 整个程序结束时 |
本地自动变量 | 代码执行到该定义块 | 该定义块执行完毕 |
本地静态static变量 | 相关函数第一次执行时 | 整个程序结束时 |
2.除了支持静态static和自动创建的对象,C++还允许我们使用动态分配对象。动态分配对象的生命周期在创建出来以后就是独立的;它们会一直存在直到被明确的释放。
3.合理释放动态对象可谓是相当多的bug的来源。为了让动态对象的使用更加安全,the library(C++库)定义了两种用来管理动态分配对象的指针类型。智能指针确保对象能够在合适的时候自动释放。
一般的,C++程序只使用静态或堆栈存储。
1.静态存储用于本地静态对象(6.1.1,p205),类静态数据成员(7.6,p300),以及任何函数外定义的变量。
?->什么是本地静态对象?
size_t count_calls(){
static size_t ctr = 0;
return ++ctr;//本地静态对象
}
int main(){
for(size_t i=0;i!=10;i++)
cout << count_calls() << endl;
return 0;
}
程序会输出1-10.同时,count_calls()里的static size_t ctr = 0,就是一个本地静态对象。
在程序第一次经过ctr的时候,ctr就被创建并赋值0.之后的for循环一直对这个值做增加操作。当count_calls重新执行时,ctr已经存在(因为是static的),所以不会再创建。参照开头变量生命周期的表格。
?->什么是类静态数据成员?
class Account {
public:
void calculate() { amount += amount * interestRate; }
static double rate() { return interestRate };
static void rate(double);
private:
std::string owner;
double amount;
static double interestRate;//类静态数据成员
static double initRate();
};
类中,静态的数据成员存在于所有对象之外。对象并不包含和静态数据成员相关联的数据。因此,每个Account对象各自有两个专属数据成员——owner和amount。但是静态的interestRate对象是所有的Account类一起用。
相似的,静态成员函数不受任何对象的束缚。同时他们没有this指针。所以,static成员函数不一定非要声明成常量const,而我们指的也可能不是静态成员内的this。这种限制在调用非静态成员的时候,既适用于显式的this,也适用于隐式的this。【翻译存在问题】
2.堆栈内存则一般则用于内部函数定义的非静态对象。
对象通过编译器的创建、销毁操作自动分配其使用静态还是堆栈存储。堆栈对象只在其块被定义为正在执行的时候才存在于堆栈。静态对象则在被使用之前就分配好,并在程序结束时销毁。
除了堆栈和静态存储,每个程序还有一个自己专门使用的内存池。这个内存池被称为自由存储区(free store)或堆(heap)。程序对动态分配——程序在运行时分配——的对象使用堆。程序控制着动态对象的整个生命周期;而我们的代码必须明确的清除这些不再使用的对象。
12.1 动态内存和智能指针
在c++里,动态内存被一对操作符管理:new和delete。
new:在动态内存里对对象进行分配、设置初始化,返回一个指针给这个对象。
delete:让一个指针指向一个动态对象,删除它,并且释放其相关联的内存。
动态内存有件事很成问题,因为这样做非常的难以确定,我们是否在正确的时候释放了内存。一般不正确的释放方法,不是忘了释放内存——一般叫内存泄漏——就是在指针还在指向内存的时候就把它释放掉了——然后指针指向的内存就失效了。
[C++11]为了更容易(并且安全)的使用动态内存,the new library(新C++11库)提供了两种智能指针来管理动态对象。智能指针就像普通的指针,不同之处在于,智能指针会(在不需要的时候)自动删除它所指向的对象。C++11库定义了两种智能指针,区别在于管理自己基础指针的方法上有所不同:
1.shared_ptr,允许多个指针指向同一个物体
2.unique_ptr,当指向一个指针的时候,自己独自享有此指针(别的指针不能再指向它)
C++库也定义了一个伴生类:
weak_ptr
弱引用一个由shared_ptr管理的对象。
- 以上的三个指针都定义在memory.h里。
12.1.1 shared_ptr 类
就像vector,智能指针就是模板。因此,当我们创建一个智能指针的时候,我们必须供应一个额外信息——比如这一次应该是指针能指向的类型。对于每个vector,我们提供尖括号内部的类型,根据尖括号里面的名字来确定智能指针定义的种类。
shared_ptr<string>p1;//这么写shared_ptr就能指向string类型了
shared_ptr<list<int>>p2;//这么写shared_ptr指向int类型的list
智能指针默认的初始化持有空指针(2.3.2,nullptr,p53)。
在12.1.3,我们将会用额外的方法去初始化智能指针,(当然,这么做会覆盖掉默认的初始化方法)。
我们用一个智能指针的方法和用一个普通指针相似。一个非引用的智能指针,返回的类型是它指向的对象。而当我们在if中使用智能指针,它的效果是测试指针是否为空。
//若p1不为空,检查p1指向的字符串是否为空
if (p1 && p1->empty())
*p1 = "hi";//如果字符串空,给其添加新值。
表12-1列举了shared_ptr,unique_ptr的主要操作指令。
表12-2列举了shared_ptr自身特有的操作指令。
make_shared函数
想分配并使用动态内存,最安全的方法是呼叫函数make_shared。
这个函数在动态内存中呼叫并初始化动态内存,并返回一个shared_ptr类型。
和智能指针相同,make_shared也是在memory.h头文件里面的。
一旦我们想要呼叫函数make_shared,我们必须给对象指定一个我们想要创建的类型。我们用了和使用模板类时采用的同样的方法,根据尖括号里面的内容,判断函数类型。
//shared_ptr指向一个值42的int
shared_ptr<int>p3 = make_shared<int>(42);
//p4指向值为9999999999的字符串。
shared_ptr<string>p4 = make_shared<string>(10,'9');
//p5指向初始化为0的int
shared_ptr<int>p5 = make_shared<int>();
就像顺序容器emplace成员一样(9.3.1,p345),make_shared使用参数来构建一个给定类型的对象。
比如,调用make_shared<string>必须传递相匹配的一个string构造函数,调用make_shared<int>可以传递任何我们用来初始化int的值,等等。而如果我们不传递任何参数,那么对象会持有初始化值。
当然平常我们用auto,就可以更简单的定义make_shared的构建类型啦。
//p6指向了一个动态分配的,空的string容器(vector<string>)
auto p6 = make_shared<vector<string>>()
复制并且分配shared_ptrs
当我们复制或者分配一个shared_ptr时,每个shared_ptr都会一直跟踪有多少其他的shared_ptr和自己指向同一个对象。
auto p = make_shared<int>(42);//此时p指向的对象有了一个使用者(即p)
auto q(p); //q指向和p相同的对象。此时p和q指向的对象有了两个使用者(p和q)
【表12.1】shared_ptr和unique_ptr的操作命令:
语句 | 作用 |
---|---|
shared_ptr<T> sp | 一个指向T类型的空智能(shared)指针 |
unique_ptr<T> up | 一个指向T类型的空智能(unique)指针 |
p | 用于if中,判断若返回true则指针不为空 |
*p | 间接引用p获得p指向的值 |
p->mem | 和(*p).mem作用相同 |
p.get() | 返回为p的指针。小心使用:当智能指针删除它的时候返回的指针指向对象将会消失 |
swap(p,q) | 交换指针p,q |
p.swap(q) | 交换指针p,q |
我们可以认为每个shared_ptr函数好像自带一个计数器,通常用来作为一个引用计数。当我们复制一个shared_ptr时,计数增加。
例如,计数器和shared_ptr相关联,当我们初始化另一个shared_ptr、把它作为一个赋值的右操作数、或者把它传递出去、再或者从一个函数返回得到它的值,计数器都会增加。
而当我们分配了一个新值给shared_str并且当此shared_ptr销毁的时候,例如当一个本地shared_ptr变量超出范围,计数器都会减少。
一旦一个shared_ptr的计数器值到了0,这个shared_ptr就会自动释放它管理的对象:
auto r = make_shared<int>(42)//此时r指向的对象有了一个使用者
r = q; //分配给r,让它指向一个不同的地址
//q指向的对象的计数器会+1
//r指向的对象的计数器会-1
//r过去指向的对象再也没有用户了,该对象会被自动删除。
上边的例子里我们分配了一个int并且在r中储存了一个指针指向int。接下来,我们给r一个新值(q)。在本例中,r是唯一指向过我们先前分配的空间的shared_ptr。而原先的int在把q的值赋给r时,自动释放掉了。
shared_ptr 自动销毁其对象
当指向相同对象的最后一个shared_ptr被销毁时,该shared_ptr类会自动销毁其指向的对象。它通过这种另类的特殊成员函数执行析构。和它的构造函数类似,每个类都自带一个析构函数。就如同作为一个构造函数控制初始化那样,析构函数也会在该类的类型对象销毁的时候控制析构。
【表12.2】shared_ptr的特有操作
语句 | 作用 |
---|---|
make_shared<T>(args) | 返回一个指向类型T动态分配对象的shared_str。 |
updating……