动态内存1(动态内存与智能指针)

静态内存用来保存局部 static 对象、类 static 数据成员 以及任何定义在函数之外的变量。栈内存用来存储定义在函数内部的非 static 对象。分配在静态或栈内存中的对象由编译器自动创建和销毁。对于栈对象,仅在其定义的程序块运行时才存在;static 对象在使用之前分配,在程序结束时销毁。

除了静态内存和栈内存,每个程序还拥有一个内存池。这部分内存被称作自由空间。程序用堆来存储动态分配的对象——即,那些在程序运行时分配的对象。动态对象的生存周期由程序来控制,即当动态对象不再使用时,我们的代码必须显示的销毁它们。

 

动态内存与智能指针:

c++中东台内存时通过一对运算符来完成的:new,在动态内存中为对象分配内存空间并返回一个指向该对象的指针,我们可以选择对象进行初始化;delete,接受一个动态对象的指针,销毁该对象并释放与之关联的内存。

c++11标准库中提供了两种智能指针类型来管理动态内存对象。智能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象。标准库提供的这两种智能指针的区别在于管理底层指针的方式:shared_ptr 允许多个指针指向同一个对象;unique_ptr 则 “独占” 所指向的对象。标准库还定义了一个名为 weak_ptr 的伴随类,它是一个弱引用,指向 shared_ptr 所管理的对象。这三种类型都定义在 memory 头文件中

 

shared_ptr 类:

智能指针也是模板:

 1 #include <iostream>
 2 #include <memory>
 3 #include <list>
 4 using namespace std;
 5 
 6 int main(void){
 7     shared_ptr<string> p1;//shared_ptr,可以指向string
 8     shared_ptr<list<int>> p2;//shared_ptr,可以指向int的list
 9 
10     if(p1 && p1->empty()) *p1 = "hi";//如果p1指向一个空string,解引用p1,将一个新值赋予string
11     
12     return 0;
13 }
View Code

注意:默认初始化的智能指针中保存着一个空指针

智能指针的使用方式与普通指针类似。解引用一个智能指针返回它指向的对象。如果在一个判断条件中使用智能指针,效果就是检查它是否为空

 

shared_ptr 和 unique_ptr 都支持的操作:

shared_ptr<T> sp 空智能指针,可以指向类型为 T 的对象

unique_ptr<T> up

p        将 p 用作一个条件判断,若 p 指向一个对象,则为 true

*p         解引用 p,获得它指向的对象

p->mem       等价于 (*p).mem

p.get()       返回 p 中保存的指针。要小心使用,若智能指针释放了其对象,返回的指针所

          指向的对象也就消失了。通常用于得到 p 中保存的传统风格指针,因为用 shared_ptr

          直接给普通指针赋值或初始化是非法的

swap(p, q)      交换 p 和 q 中的指针

p.swap(q)

 

shared_ptr 独有的操作:

make_shared<T>(args)  返回一个 shared_ptr,指向一个动态分配的类型为 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.unique()       若 p.use_count() 为 1,返回 true,否则返回 false

p.use_count()      返回与 p 共享对象的智能指针数量;可能很慢,主要用于调试

 

make_shared 函数:

最安全的分配和使用动态内存的方法是调用一个名为 make_shared 的标准库函数,我们应该尽量适用 make_shared:http://blog.csdn.net/coolmeme/article/details/43405155

 1 #include <iostream>
 2 #include <memory>
 3 #include <list>
 4 using namespace std;
 5 
 6 int main(void){
 7     //指向一个值为42的int的shared_ptr
 8     shared_ptr<int> p1 = make_shared<int>(42);
 9     cout << *p1 << endl;//42
10     //p2指向一个值为"999"的string
11     shared_ptr<string> p2 = make_shared<string>(3, '9');
12     cout << *p2 << endl;//999
13     //p3指向一个值初始化的int,即0
14     shared_ptr<int> p3 = make_shared<int>();
15     cout << *p3 << endl;//0
16 
17     return 0;
18 }
View Code

注意:使用 make_shared 时,必须指定要创建的对象类型。定义方式与模板类相同,在函数名后跟一个尖括号,在其中给出类型

类似顺序容器的 emplace 成员,make_shared 用其参数来构造给定类型的对象。因此给的参数必须符合对应类型的构造规则

 

当然,用 auto 定义一个对象来保存 make_shared 的结果会简单很多:

 1 #include <iostream>
 2 #include <memory>
 3 #include <list>
 4 using namespace std;
 5 
 6 int main(void){
 7     //指向一个值为42的int的shared_ptr
 8     auto p1 = make_shared<int>(42);
 9     cout << *p1 << endl;//42
10     //p2指向一个值为"999"的string
11     auto p2 = make_shared<string>(3, '9');
12     cout << *p2 << endl;//999
13     //p3指向一个值初始化的int,即0
14     auto p3 = make_shared<int>();
15     cout << *p3 << endl;//0
16 
17     return 0;
18 }
View Code

 

shared_ptr 的拷贝和赋值:

当进行拷贝和赋值操作时,每个 shared_ptr 都会记录有多少个其它 shared_ptr 指向相同的对象:

 1 #include <iostream>
 2 #include <memory>
 3 using namespace std;
 4 
 5 int main(void){
 6     auto p = make_shared<int>(42);//p指向的对象此时只有p一个引用者
 7     auto q(p);//p和q指向相同的对象,此对象有两个引用者
 8     cout << p.use_count() << endl;//2
 9     cout << q.use_count() << endl;//2
10 
11     return 0;
12 }
View Code

 

我们可以认为每个 shared_ptr 都有一个关联的计数器,通常称其为引用计数。无论何时我们拷贝一个 shared_ptr,计数器都会递增。如,当用一个 shared_ptr 初始化另一个 shared_ptr,或将它作为参数传递给另一个函数以及作为函数的返回值时,它所关联的计数器都会递增。当我们给 shared_ptr 赋予一个新值或是 shared_ptr 被销毁(如,一个局部的 shared_ptr 离开其作用域)时,计数器都会递减。一旦一个 shared_ptr 的计数器变为 0,它就会自动释放自己所管理的对象:

 1 #include <iostream>
 2 #include <memory>
 3 using namespace std;
 4 
 5 int main(void){
 6     auto p = make_shared<int>(42);//p指向的对象此时只有p一个引用者
 7     auto q(p);//p和q指向相同的对象,此对象有两个引用者
 8     cout << p.use_count() << endl;//2
 9     cout << q.use_count() << endl;//2
10 
11     q = make_shared<int>(1024);//给q开一个新的内存,q此时不再和p一起指向存储42那块内存
12     cout << p.use_count() << endl;//1
13     cout << q.use_count() << endl;//1
14 
15     p = q;//给p赋值,此时p和q同时指向存储1024的这块内存
16     cout << q.use_count() << endl;//2 递增q指向的对象的引用计数
17     //递减原来指向的对象的引用计数,p原来指向的对象引用计数变为0,自动释放该对象的内存(存储42的这块内存)
18 
19     return 0;
20 }
View Code

 

shared_ptr 自动销毁所管理的对象:

当指向对象的最后一个 shared_ptr 被销毁时,shared_ptr 会自动通过析构函数销毁此对象 。由于在最后一个 shared_ptr 销毁之前内存都不会释放,保证 shared_ptr 在无用之后不再保存就十分·重要了。如果忘记销毁程序不再需要的 shared_ptr,程序仍会正确执行,但会浪费内存。shared_ptr 在无用之后仍然保留的的一种可能情况是,你将 shared_ptr 存放在一个容器中,随后重排了容器,从而不再需要某些元素。这种情况下,你应该确保用 erase 删除那些不再需要的 shared_ptr 元素

 

使用了动态生存期的资源的类:

程序使用动态内存处于以下三种原因之一:

1.程序不知道自己需要使用多少对象

2.程序不知道所需对象的准确类型

3.程序需要在多个对象间共享数据

 

我们来分析一波第 3 种情况:

我们之前用过的类中,分配的资源都与对应对象生存期一致。如:每个 vector “拥有” 其自己的元素。当我们拷贝一个 vector 时,原 vector 和 副本 vector 中的元素是相互分离的:

1 vector<string> v1;//空vector
2 {//新作用域
3     vector<string> v2 = {"f", "jf"};
4     v1 = v2;//从v2拷贝元素到v1中
5 }//离开作用域,v2被销毁,其中元素也被销毁
6 //v1中有2个元素,是原来v2中元素的拷贝
View Code

注意:由一个 vector 分配的元素只有当这个 vector 存在时才存在。当一个 vector 被销毁时,其中的元素也都被销毁

 

但某些类分配的资源具有与原对象相独立的生存期。如,假定我们希望定义一个名为 Blod 的类,保存一组元素。与容器不同,我们希望 Blod 对象的不同拷贝之间共享相同的元素。即,当我们拷贝一个 Blod 时,原 Blod 对象及其拷贝应该引用相同的底层元素。

通常,如果两个对象共享底层数据,当某个对象被销毁时,我们不能单方面销毁底层数据:

1 Blod<string> b1;//空Blod
2 {//新作用域
3     Blod<string> b2 = {"f", "jf"};
4     v1 = v2;//从b2拷贝元素到b1中
5 }//离开作用域,b2被销毁,但其中元素不能销毁
6 //b1指向最初由b2创建的元素
View Code

注意:b1 和 b2 共享相同的元素,当 b2 离开作用域时,这些元素必须保留,因为 b1 仍任在使用它们

 

定义一个管理 string 的 Blod 类,命名为 strBlod:

  1 #include <iostream>
  2 #include <memory>
  3 #include <vector>
  4 using namespace std;
  5 
  6 class strBlod{
  7 public:
  8     typedef std::vector<std::string>::size_type size_type;
  9     strBlod();
 10     strBlod(std::initializer_list<std::string> il);
 11     ~strBlod();
 12 
 13     size_type size() const {
 14         return data->size();
 15     }
 16 
 17     bool empty() const {
 18         return data->empty();
 19     }
 20 
 21     //添加和删除元素
 22     void push_back(const std::string &t){
 23         data->push_back(t);
 24     }
 25     void pop_back();
 26 
 27     //元素访问
 28     std::string& front();
 29     std::string& front() const;
 30 
 31     std::string& back();
 32     std::string& back() const;
 33 
 34     int get_use_count() const{//得到shared_ptr成员data的引用计数
 35         return data.use_count();
 36     }
 37 
 38 private:
 39     std::shared_ptr<std::vector<std::string>> data;
 40 
 41     //如果data[i]不合法,抛出一个异常
 42     void check(size_type i, const std::string &msg) const;
 43 
 44     std::string& front_display() const;
 45     std::string& back_display() const;
 46     
 47 };
 48 
 49 strBlod::strBlod() : data(make_shared<vector<string>>()) {}//默认构造函数
 50 strBlod::strBlod(initializer_list<string> il) : data(make_shared<vector<string>>(il)) {}
 51 strBlod::~strBlod() {}
 52 
 53 void strBlod::check(size_type i, const string &msg) const {
 54     if(i >= data->size()) throw out_of_range(msg);
 55 }
 56 
 57 string& strBlod::front(){
 58     return front_display();
 59 }
 60 
 61 string& strBlod::front() const{
 62     return front_display();
 63 }
 64 
 65 string& strBlod::front_display() const{
 66     //如果vector为空,check会抛出一个异常
 67     check(0, "front on empty strBlod!");
 68     return data->front();
 69 }
 70 
 71 string& strBlod::back(){
 72     return back_display();
 73 }
 74 
 75 string& strBlod::back() const{
 76     return back_display();
 77 }
 78 
 79 string& strBlod::back_display() const{
 80     check(0, "back on empty strBlod!");
 81     return data->back();
 82 }
 83 
 84 void strBlod::pop_back(){
 85     check(0, "pop_back on empty strBlod!");
 86     data->pop_back();
 87 }
 88 
 89 int main(void){
 90     strBlod b1;//b1执行默认构造
 91     cout << b1.get_use_count() << endl;//1
 92 
 93     {
 94         strBlod b2({"hello", "word", "!"});
 95         cout << b2.get_use_count() << endl;//1
 96         b1 = b2;//b1和b2共享元素
 97         cout << b1.get_use_count() << endl;//2
 98         cout << b1.get_use_count() << endl;//2
 99         // 当我们拷贝、赋值或销毁一个 strBlod 对象时,它的 shared_ptr 成员会被拷贝、赋值或销毁
100     }
101     // string s = b2.front();//错误,离开了b2的作用域,b2被销毁了
102     //b1指向原本由b2创建的元素
103 
104     cout << b1.get_use_count() << endl;//1
105     string s = b1.front();
106     cout << s << endl;//hello
107 
108     return 0;
109 }//离开b1的作用域,b1被销毁
110 //由strBlod构造函数分配的vector已经没有strBlod对象指向它,此时被自动销毁
View Code

注意:当我们拷贝、赋值或销毁一个 strBlod 对象时,它的 shared_ptr 成员会被拷贝、赋值或销毁

如果一个 shared_ptr 的引用计数变为 0,它所指像的对象会被自动销毁。因此,对于由 strBlod 构造函数分配的 vector,当最后一个指向它的 strBlod 对象被销毁时,它也会随之被自动销毁

 

直接管理内存:

使用 new 动态分配和初始化对象:

 1 #include <iostream>
 2 #include <vector>
 3 using namespace std;
 4 
 5 int main(void){
 6     int *pi = new int;//pi指向一个动态分配的,未初始化的对象
 7     cout << *pi << endl;//默认情况下,动态内存分配的对象是默认初始化的
 8     //即,内置类型或组合类型的对象的值将是未定义的(随机值),而类类型对象将用默认构造函数进行初始化
 9     string *ps = new string;//初始化为空string
10 
11     int *p1 = new int(1024);//构造初始化,p1指向一个值为1024的int对象
12     string *ps1 = new string(3, '9');//ps1指向一个值为"999"的string对象
13 
14     vector<int> *pv1 = new vector<int>{0, 1, 2, 3, 4};//列表初始化
15 
16     //值初始化(类型名后面跟一对括号)
17     string *ps2 = new string;//默认初始化为空string
18     string *ps3 = new string();//值初始化为空string
19     int *p2 = new int();//值初始化为0
20     cout << *p2 << endl;//0
21 
22     //使用auto推断
23     int obj = 1024;
24     int obj1 = 1, obj2 = 2, obj3 = 3;
25     auto p4 = new auto(obj);//p4指向一个与obj类型相同的对象
26     auto p5 = new auto{obj3, obj2, obj1};//c++prime 5th上说这种用法是错误的,只能有单个初始化器,但我用g++11编译通过了
27     //然而并不知道这个推断出来的是什么类型,也不是int*
28     // cout << p5[0] << endl;//错误,p5不是int*类型
29 
30     // 动态分配const对象
31     const int *p6 = new const int(1024);//分配并初始化一个const int
32     // *p6 = 0;//错误,p6指向的对象是const int
33     const string *ps4 = new const string;//分配并默认初始化一个const的空string
34 
35     return 0;
36 }
View Code

注意:在自由空间分配的内存是无名的,因此 new 无法为其分配的对象命名,而是返回一个指向该对象的指针

默认情况下,动态内存分配的对象是默认初始化的,即,内置类型或组合类型的对象的值将是未定义的(随机值),而类类型对象将用默认构造函数进行初始化

对于定义了自己的构造函数的类类型来说,要求值初始化是没有意义的,不管采用什么形式,对象都会通过默认构造函数类初始化

对于内置类型,应该尽量使用值初始化来代替默认初始化,因为其默认初始化是未定义的

如果我们提供了一个括号包围的初始化器,就可以使用 auto 从此初始化器来推断类型。但是由于编译器要用初始化器的类型来推断分配的类型,只有当括号中仅有单一初始化器是才可以使用 auto

动态分配 const 对象时对于没有默认构造函数的类类型,必须显示初始化

 

内存耗尽:

一旦一个程序用完了它所有可用的内存,new 表达式就会失败。默认情况下,如果 new 不能分配所需求的内存空间,会抛出一个类型为 bad_alloc 的异常。我们可以改变使用 new 的方式来阻止它抛出异常:

1     int *p1 = new int;//如果分配失败,new 抛出std::bad_alloc
2     int *p2 = new (nothrow) int;//如果分配失败,new 返回一个空指针
3     //定位new表达式允许我们像new传递额外的参数
View Code

 

释放动态内存:

指针值和delete:

我们传递给 delete 的指针必须指向动态分配的内存,或者是一个空指针。释放一块并非 new 分配的内存,,或者将相同的指针释放多次,其行为是未定义的:

 1 #include <iostream>
 2 using namespace std;
 3 
 4 int main(void){
 5     int i, *pi1 = &i, *pi2 = nullptr;
 6     double *pd = new double(33), *pd2 = pd;
 7     // delete i;//错误,i不是应该指针
 8     // delete pi1;//能通过编译,但是该行为是未定义的,pi1指向一个局部变量
 9     delete pd;
10     // delete pd2;能通过编译,但是该行为是未定义的,p2指向的内存已经被释放了
11     delete pi2;//释放一个空指针总是没有错误的
12 
13     const int *p = new const int(1024);
14     delete p;//正确,释放一个const对象
15 
16     return 0;
17 }
View Code

注意:通常情况下,编译器不能分辨一个指针指向的是静态还是动态分配的对象。类似的,编译器也不能分辨一个指针所指向的内存是否已经释放了。对于这些 delete 表达式,大多数编译器会编译通过,尽管它们是错误的

 由内置指针管理的动态内存被显示释放前是一直存在的。当一个只想动态内存的指针离开其作用域时其关联的内存不会被自动释放

 

动态内存管理常见错误:

1.忘记 delete 内存,导致内存泄漏

2.使用已经释放掉的对象。通过在释放内存后将指针置为空,有时可以检测出这种错误

3.同一块内存释放多次

 

delete 之后重置指针:

在 delete 之后,指针就变成了空悬指针。可以在 delete 之后将 nullptr 赋予指针,这样就清楚地指出指针不指向任何对象。但是,动态内存可能有多个指针指向相同的内存。在 delete 之后重置指针的方法只对这个指针有效,对其它任何仍指向(已释放的)内存的指针是没有作用的:

1     int *p(new int(42));
2     auto q = p;
3     delete p;//p和q均无效
4     p = nullptr;//指出p不再绑定到任何对象
View Code

注意:此时 q 已经无效了,但重置 p 对 q 没有任何影响,折意味着 q 并没有被表示为 nullptr

 

shared_ptr 和 new 结合使用:

 1 #include <iostream>
 2 #include <memory>
 3 using namespace std;
 4 
 5 shared_ptr<int> clone(int p){
 6     // return new int(p);//错误,将一个int*隐式转换为shred_ptr<int>
 7     return shared_ptr<int>(new int(p));//正确,显示地用int*创建shared_ptr<int>
 8 }
 9 
10 int main(void){
11     shared_ptr<double> p1;//shared_ptr可以指向一个double
12     shared_ptr<int> p2(new int(1024));//p2指向一个值为42的int
13     //接受指针参数的智能指针构造函数是explicit的,我们不能将一个
14     //内置指针隐式转化一个智能指针,必须使用直接初始化形式来初始化一个智能指针
15     // shared_ptr<int> p3 = new int(1024);//错误,必须使用直接初始化形式
16 
17     return 0;
18 }
View Code

注意:接受指针参数的智能指针构造函数是 explicit 的,我们不能将一个内置指针隐式转化一个智能指针,必须使用直接初始化形式来初始化一个智能指针

智能指针默认使用 delete 释放它所关联的对象,默认情况下一个用来初始化智能指针的普通指针必须指向动态内存。

我们也可以将智能指针绑定到一个指向其它类型的资源的指针上,但是为了这么做,必须提供自己的操作来替代 delete

 

定义和改变 shared_ptr 的其它方法:

shared_ptr<T> p(q)    p 管理内置指针 q  所指向的对象:q 必须指向 new 分配的内存且能够转换

              为 T* 类型

shared_ptr<T> p(u)    p 从 unique_ptr u 那里接管了对象的所有权:将 u 置为空

shared_ptr<T> p(q, d)   p 接管了内置指针 q 所指向的对象的所有权。q 必须能转为 T* 类型。p 将

              使用可调用对象 d 来代替 delete

shared_ptr<T> p(p2, d)   p 是 shared_ptr p2 的拷贝,且用可调用对象 d 来替代 delete

p.reset()           若 p 是唯一指向其对象的 shared_ptr,reset 会释放此对象。若传递了可选

p.reset(q)         参数指针 q,会令 p 指向 q,否则会将 p 置为空。若还传递了可选参数 d,

p.reset(q, d)        将会调用 d 而不是 delete 来释放 q

 

不要混合使用普通指针和智能指针:

 1 #include <iostream>
 2 #include <memory>
 3 using namespace std;
 4 
 5 void process(shared_ptr<int> ptr){//值传参,拷贝后引用计数加一
 6 
 7 }//离开ptr作用域,ptr被销毁,其所指对象引用计数减一
 8 
 9 int main(void){
10     shared_ptr<int> p(new int(42));//引用计数为1
11     process(p);//拷贝p会递增它的引用计数,传入process后引用计数为2
12     int i = *p;//正确,引用计数为1
13 
14     int *x(new int(1024));//x是一个普通指针
15     // process(x);//错误,不能将int*隐式转换为shared_ptr<int>
16     process(shared_ptr<int>(x));//创建shared_ptr<int>临时变量时其引用计数为1,
17     //离开创建临时变量的表达式时临时变量会被销毁,传入process后引用计数为1
18     // int j = *p;//未定义的,x时一个空悬指针
19 
20     return 0;
21 }
View Code

注意:当将一个 shared_ptr 绑定到一个普通指针时,我们就将内存的管理交给了这个 shared_ptr。一旦这样做了,我们就不应该再使用内置指针来访问 shared_ptr 所指向的内存了

显示 / 隐式转换时即创建了一个对应的临时变量

离开创建临时变量的表达式时,该表达式中创建的临时变量会被销毁:

 1 #include <iostream>
 2 #include <memory>
 3 using namespace std;
 4 
 5 class gel{
 6 friend ostream& operator<<(ostream&, const gel&);
 7 
 8 private:
 9     int x, y;
10 
11 public:
12     gel(int a, int b) : x(a), y(a) {}
13     gel(int a) : gel(a, 0) {}
14     gel() : gel(0, 0) {}
15     ~gel() {
16         cout << "~gel" << endl;
17     }
18     
19 };
20 
21 ostream& operator<<(ostream &os, const gel &it){
22     cout << it.x << " " << it.y;
23     return os;
24 }
25 
26 int main(void){
27     gel(1, 2);//创建一个gel临时变量
28 
29     auto g = gel(1, 2);//g离开其作用域时被销毁
30     cout << "===" << endl;
31 
32     gel gg(1, 2);//gg离开其作用域时被销毁
33     /*输出:
34         ~gel
35         ===
36         ~gel
37         ~gel
38     */
39     return 0;
40 }
View Code

 

不要使用 get 初始化另一个智能指针或为智能指针赋值:

 1 #include <iostream>
 2 #include <memory>
 3 using namespace std;
 4 
 5 int main(void){
 6     shared_ptr<int> p(new int(42));//引用计数为1
 7     int *q = p.get();//正确,但使用时要注意,不要让它管理的指针被释放
 8     cout << q << endl;
 9     {//新作用域
10         shared_ptr<int>(q);//未定义,两个独立的(不是用其中一个智能指针初始化另一个智能指针)shared_ptr指向相同的内存
11     }//程序块结束,q被销毁,它指向的内存被释放
12     //此时p时一个空悬指针了,而且当p的作用域结束时,p还会被二次delete!!!
13     int foo = *p;//未定义,p指向的内存已经被释放了
14 
15     shared_ptr<int> g(new int(12));
16     shared_ptr<int> gg = shared_ptr<int>(g.get());
17 
18     cout << g << endl;//0x2a911f8
19     cout << gg << endl;//0x2a911f8
20     //两个独立的shared_ptr指向形同的内存
21 
22     cout << g.use_count() << endl;//1
23     cout << gg.use_count() << endl;//1
24     //g和q是独立创建的,因此它们的引用计数都是1
25 
26     return 0;
27 }
View Code

注意:get 通常用于将 shared_ptr 中保存的值赋给普通指针,因为用 shared_ptr 直接给普通指针赋值或初始化是非法的,反之亦然

只有在确定代码不会 delete 指针的情况下才能用 get

永远不要用 get 初始化另一个智能指针或为另一个智能指针赋值

两个相互独立但指向相同的内存的 shared_ptr 各自的引用计数都是 1,着意味着当其中一个自动销毁但另一个没被销毁时,没被销毁的 shared_ptr 会成为空悬指针

 

其它 shared_ptr 操作:

 1 #include <iostream>
 2 #include <memory>
 3 using namespace std;
 4 
 5 int main(void){
 6     auto p = make_shared<int>(1024);
 7     // p = new int(0);//错误,不能将一个普通指针赋给shread_ptr
 8     p.reset(new int(0));//正确,p指向一个新对象
 9 
10     if(!p.unique()) p.reset(new int(*p));//我们不是唯一用户,分配新拷贝
11     //p此时的值和原来相同,但不再指向原来的对象
12     *p += 1;//现在我们自动自己是唯一用户,可以改变对象
13 
14     return 0;
15 }
View Code

注意:与赋值类似,reset 会更新引用计数,如果需要的话,会释放 p 指向的对象。reset 经常与 unique 一i其使用,来控制多个 shared_ptr 共享的对象

 

智能指针和异常:

1 void f(){
2     int ip = new int(42);
3     //...//此处发生一个异常且未在f中被捕获
4     delete ip;
5 }
View Code

注意:使用内置指针管理内存,且在 new 之后再对应的 delete 之前发生了异常,则内存不会被释放。但如果上例中 ip 是一个 shared_ptr,则仍会在其作用域结束时正常释放

 

删除器:

当一个 shared_ptr 管理的对象被释放时,默认是进行 delete 操作,而 delete 操作是调用对应对象的析构函数来完成的。这意味这对于分配了资源而又没有定义析构函数的类,我们需要定义一个函数来代替 delete 完成对 shared_ptr 中保存的指针进行释放操作,即删除器:

 1 #include <iostream>
 2 using namespace std;
 3 
 4 struct destination;//表示我们正在连接什么
 5 struct connection;//使用连接所需的信息
 6 connection connect(destination*);//打开连接
 7 void disconnect(connection);//关闭给定的连接
 8 
 9 void end_connection(connection *p){//删除器
10     disconnect(*p);
11 }
12 
13 void f(destination &d /*其它参数*/){
14     connection c = connect(&d);
15     shared_ptr<connection> p(&c, end_connection);
16     //使用连接
17     //当f推出时,connection会被正确关闭
18 }
View Code

注意:当 p 被销毁时,它不会对自己保存的指针执行 delete,而是直接调用 end_connection

 

智能指针的陷阱:

智能指针的安全性是建立在正确使用的前提下的,我们必须坚持一些基本规范:

不使用相同的内置指针初始化(或 reset)多个智能指针。这会造成前面我们提到的相互独立的 shared_ptr 之间共享相同地址的问题

不 delete get() 返回的指针

不适用 get() 初始化或 reset 另一个智能指针

如果使用 get()  返回的指针,记住当最后一个对应的 shared_ptr 销毁后,之前 get() 返回的的指针就无效了

如果使用智能指针管理的不是 new 分配的内存,必须传给它一个删除器

注意 shared_ptr 的循环引用问题:http://blog.csdn.net/y1196645376/article/details/53023848

 

unique_ptr:

与 shared_ptr 不同,某个时刻只能有一个 unique_ptr 指向给定的对象。当 unique 被销毁时,它所指向的对象也被销毁

unique_ptr 操作:

unique_ptr<T> u1    空 unique_ptr,可以指向一个类型为 T 的对象。u1 会使用 delete 来释放它的

unique_ptr<T, D> u2     指针;u2 会使用一个类型为 D 的可调用对象来释放它的指针,初始化 u2 时

            除了需要一个能转化成 T* 类型的对象外还需要一个 D 类型的对象来代替 delete

unique_ptr<T, D> u(d)   空 unique_ptr,指向类型为 T 的对象,用类型为 D 的对象 d 代替 delete

u = nullptr         释放 u 指向的对象,将 u 置为空

u.release()        u 放弃对指针的控制权,返回指针,并将 u 置为空

u.reset()         释放 u 指向的对象

u.reset(q)          如果提供了内置指针 q,令 u 指向这个对象,否则将 u 置为空

u.reset(nullptr)

 

初始化 unique_ptr 必须采用直接初始化形式:

unique_ptr<double> p1;//可以指向一个double的unique_ptr
unique_ptr<int> p2(new int(42));//p2指向一个值为42的int

 

由于一个 unique_ptr 拥有它指向的对象,因此 unique_ptr 不支持普通的拷贝或赋值操作:

 1 #include <iostream>
 2 #include <memory>
 3 using namespace std;
 4 
 5 int main(void){
 6     unique_ptr<string> p1(new string("abc"));
 7     // unique_ptr<string> p2(p1);//错误,unique_ptr不支持拷贝 unique_ptr(const unique_ptr&) = delete;
 8     unique_ptr<string> p3;
 9     // p3 = p1;//错误,unique_ptr不支持赋值 unique_ptr& operator=(const unique_ptr&) = delete;
10 
11     return 0;
12 }
View Code

 

unique_ptr 是通过 release 或 reset 将指针的所有权从一个 (非const) unique_ptr 转移给另一个 unique_ptr 的:

 1 #include <iostream>
 2 #include <memory>
 3 using namespace std;
 4 
 5 int main(void){
 6     unique_ptr<string> p1(new string("abc"));
 7     //将所有权从p1转移到p2
 8     unique_ptr<string> p2(p1.release());//p1被置为空,p2保存了原来p1中的指针
 9 
10     unique_ptr<string> p3(new string("jfk"));
11     //将所有权从p3专移到p2
12     p2.reset(p3.release());//reset释放了p2原来指向的内存
13 
14     return 0;
15 }
View Code

注意:如果 unique_ptr 不为空,则调用 reset 会释放其原来所指的内存。而 release 则不会释放 unique_ptr 原来所指的内存。调用 release 会切断 unique_ptr 和它原来所指对象间的联系,如果我们不是用另一个智能指针来保存 release 返回的指针,则我们的程序就要负责资源的释放:

p2.release();//错误,p2 不会释放内存,而且我们丢失了指针

auto p = p2.release();//正确,但我们必须记得 delete(p)

 

传递 unique_ptr 参数和返回 unique_ptr:

不能拷贝 unique_ptr 的规则有一个例外:我们可以拷贝或赋值一个将要销毁的 unique_ptr:

从函数返回一个 unique_ptr:

1 unique_ptr<int> clone(int p){
2     return unique_ptr<int>(new int(p));
3 }
View Code

还可以返回一个局部对象的拷贝:

1 unique_ptr<int> clone(int p){
2     unique_ptr<int> ret(new int(p));
3     return ret;
4 }
View Code

注意:这两段代码,编译器都知道要返回的对象将要被销毁。在此种情况下,编译器执行一种特殊的 "拷贝" (移动构造函数)

 

向 unique_ptr 传递删除器:

类似 shared_ptr,unique_ptr 默认情况下用 delete 释放它指向的对象。同样的,我们可以重载一个 unique_ptr 中默认的删除器:

unique_ptr<objT, delT> p(new objT, fcn);

//p 指向一个类型为 objT 的对象,并使用一个类型为 delT 的对象 fnc 来释放 objT 对象

类似于关联容器中重载比较运算函数,等于判断函数,哈希函数等

1 void f(destination &d /*其它需要的参数*/){
2     connection c = connect(&d);//打开连接
3     //当p被销毁时,连接会自动关闭
4     unique_ptr<connection, decltype(end_connection)*> p(&c, end_connection);
5     //decltype只是返回了一个函数类型,必须添加*来指出我们正在使用一个函数指针
6     //使用连接
7     //当f退出时(即使是由于异常而退出),connection会被正常关闭
8 }
View Code

 

weak_ptr:

weak_ptr 指向一个 shared_ptr 管理的对象。将一个 weak_ptr 绑定到一个 shared_ptr 不会改变 shared_ptr 的引用计数。一旦最后一个指向对象的 shared_ptr 被销毁,即便有 weak_ptr 指向对象,对象也还是会被释放的

weak_ptr 的操作:

weak_ptr<T> w    空 weak_ptr 可以指向类型为 T 的对象

weak_ptr<T> w(sp)   与 shared_str sp 指向相同对象的 weak_ptr。T 必须能转化为 sp 指向的对象的类型

w = p          p 可以是一个 shared_ptr 或一个 weak_ptr。赋值后 w 与 p 共享对象

w.reset()       将 w 置为空

w.use_count()      与 w 共享对象的 shared_ptr 的数量

w.expired()      若 w.use_count 为 0,返回 true,否则返回 false

w.lock()         如果 w.expired() 为 true,返回一个空 shared_ptr,否则返回一个指向 w 的对象

           的 shared_ptr

 

创建一个 weak_ptr 时,要用一个 shared_ptr 来初始化它:

 1 #include <iostream>
 2 #include <memory>
 3 using namespace std;
 4 
 5 int main(void){
 6     auto p = make_shared<int>(42);
 7     weak_ptr<int> wp(p);//wp与p指向相同的对象
 8     cout << p.use_count() << endl;//1 wp弱共享p,p的引用计数未改变
 9     weak_ptr<int> a;
10     a = p;//拷贝赋值
11 
12     int *cnt = new int(1024);
13     weak_ptr<int> b;
14     // b = cnt;//只能通过weak_ptr或shared_ptr对象拷贝赋值
15     // weak_ptr<int> c(cnt);//错误,只能通过weak_ptr或shared_ptr对象直接构造赋值
16     weak_ptr<int> d(wp);
17 
18     return 0;
19 }
View Code

 

使用 weak_ptr 时,由于我们不确定其所指对象是否存在,要先调用 lock 函数检查一下其所指对象是否存在:

1 if(shared_ptr<int> np = wp.lock()){//如果np部位空则条件成立
2         //在if中,np和wp共享对象
3     }
View Code

 

posted @ 2018-01-31 20:10  geloutingyu  阅读(526)  评论(0编辑  收藏  举报