Chapter 12 手记

首页笔记:

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……

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值