动态内存与智能指针

一、shared_ptr

最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数。此函数在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr。

1 shared_ptr<int> p3 = make_shared<int>(42);
2 shared_ptr<string> p4 = make_shared<string>(10, ‘9‘);
3 shared_ptr<int> p5 = make_shared<int>();

如果我们不传递参数,对象就会进行值初始化。

shared_ptr内部有一个引用计数变量,记录有多少个其他shared_ptr指向相同的对象。计数器变为0,会自动释放自己所管理的对象。

shared_ptr在无用之后一定要销毁,否则程序仍会正确执行,但会浪费内存。shared_ptr在无用之后仍然保留的一种可能情况是,你将shared_ptr存放在一个容器中,随后重排了容器,从而不再需要某些元素。

在这种情况下,你应该确保用erase删除那些不再需要的shared_ptr元素。比如unique算法,重排输入序列,覆盖相邻的重复元素,返回一个指向不重复元素之后位置的迭代器。

二、new与delete

new

默认情况下,动态分配的对象是默认初始化。在类型名之后跟一对空括号进行值初始化。

对于类类型来说,不管采用什么形式,对象都会通过默认构造函数来初始化。

Tip:出于与变量初始化相同的原因,对动态分配的对象进行初始化通常是个好主意。

1 auto p1 = new auto(obj);          // 正确
2 auto p2 = new auto(a,b,c);       // 错误

由于编译器要用初始化器的类型来推断要分配的类型,只有当括号中仅有单一初始化器时才能使用auto。

默认情况下,如果new不能分配所要求的内存空间,它会抛出一个类型为bad_alloc的异常。我们可以改变使用new的方式来阻止它抛出异常。

1 int *p1 = new int;                       // 如果分配失败,new抛出std::bad_alloc
2 int *p2 = new  (nothrow) int;      // 如果分配失败,new返回一个空指针

这种形式的new是定位new(placement new)。

delete

delete p;     // p必须指向一个动态分配的对象或是一个空指针

通常情况下,编译器不能分辨一个指针指向的是静态还是动态分配的对象。类似的,编译器也不能分辨一个指针所指向的内存是否已经被释放了。

虽然一个const对象的值不能被改变,但它本身是可以被销毁的。想要释放一个const动态对象,只要delete指向它的指针即可:

1 const int *pci = new const int(1024);
2 delete pci;       // 正确

Tip:内置指针指向的动态内存在销毁内置指针之前必须显示释放。

使用new和delete管理动态内存存在三个常见问题:

1.忘记delete内存。

2.使用已经释放掉的对象。

3.同一块内存释放两次。

Tip:坚持只使用智能指针,就可以避免所有这些问题。

当我们delete一个指针后,指针变为无效,但在很多机器上指针仍然保存着动态内存的地址。

空悬指针是指向一块曾经保存数据对象但现在已经无效的内存的指针,本质上和未初始化指针类似。

有一种方法可以避免空悬指针的问题:释放内存后马上销毁指针。

如果我们需要保留指针,可以在delete之后将nullptr赋予指针,这样就清楚地指出指针不指向任何对象。

三、shared_ptr和new结合使用

如果我们不初始化一个智能指针,它就会被初始化为一个空指针。

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

1 shared_ptr<int> p1 = new int(1024);     // 错误
2 shared_ptr<int> p2(new int(1024));      // 正确

出于相同的原因,一个返回shared_ptr的函数不能在其返回语句中隐式转换一个普通指针:

1 shared_ptr<int> clone(int p)
2 {
3   return new int(p);      // 错误
4 }

我们必须将shared_ptr显示绑定到一个想要返回的指针上:

1 shared_ptr<int> clone(int p)
2 {
3   return shared_ptr<int>(new int(p));      // 正确
4 }

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

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

Cautions:

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

shared_ptr可以协调对象的析构,但这仅限于其自身的拷贝(也是shared_ptr)之间。这也是为什么我们推荐使用make_shared而不是new的原因。

这样,我们就能在分配对象的同时就将shared_ptr与之绑定,从而避免了无意中将同一块内存绑定到多个独立创建的shared_ptr上。

 1 void process(shared_ptr<int> ptr)
 2 {
 3     // 使用ptr
 4 }
 5
 6 shared_ptr<int> p(new int(42));
 7 process(p);
 8 int i = *p;     // 正确
 9   
10 int *x(new int(1024));
11 process(x);     //  错误
12 process(shared_ptr<int>(x));      // 合法的
13 int j = *x;     // 未定义的:x是一个空悬指针!

在上面的调用中,我们将一个临时shared_ptr传递给process。当这个调用所在的表达式结束时,这个临时对象就被销毁了。

销毁这个临时变量会递减引用计数,此时引用计数就变为0了。因此,当临时对象被销毁时,它所指向的内存会被释放。

当将一个shared_ptr绑定到一个普通指针时,我们就将内存的管理责任交给了这个shared_ptr。

一旦这样做了,我们就不应该再使用内置指针来访问shared_ptr所指向的内存了。

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

智能指针类型定义了一个名为get的函数,它返回一个内置指针,指向智能指针管理的对象。

此函数是为了这样一种情况而设计的:我们需要向不能使用智能指针的代码传递一个内置指针。

使用get返回的指针的代码不能delete此指针。

虽然编译器不会给出错误信息,但将另一个智能指针也绑定到get返回的指针上是错误的:

1 shared_ptr<int> p(new int (42));
2 int *q = p.get();
3 {
4   shared_ptr<int>(q);      // 未定义:两个独立的shared_ptr指向相同的内存
5 }
6 int foo = *p;     // 未定义:p指向的内存已经被释放了

Tip:get用来将指针的访问权限传递给代码,你只有在确定代码不会delete指针的情况下,才能使用get。

特别是,永远不要用get初始化另一个智能指针或者为另一个智能指针赋值。

3.其他shared_ptr操作

我们可以将一个新的指针赋予一个shared_ptr:

1 p = new int(1024);            // 错误
2 p.reset(new int(1024));     // 正确

reset会更新引用计数。reset成员经常与unique一起使用,来控制多个shared_ptr共享的对象。

在改变底层对象之前,我们检查自己是否是当前对象仅有的用户。如果不是,在改变之前要制作一份新的拷贝:

1 if(!p.unique())
2   p.reset(new string(*p));      // 我们不是唯一用户;分配新的拷贝
3 *p += newVal;      // 我们是唯一用户,可以改变对象的值

四、删除器(deleter)

与管理动态内存类似,我们通常可以使用类似的技术来管理不具有良好定义的析构函数的类。

例如,假定我们正在使用一个C和C++都使用的网络库,使用这个库的代码可能是这样的:

 1 struct destination;                              // 表示我们正在连接什么
 2 struct connection;                              // 使用连接所需的信息
 3 connection connect(destination*);       // 打开连接
 4 void disconnect(connection);              // 关闭给定连接
 5 void f(destination &d /* 其他参数*/)
 6 {
 7     // 获得一个连接;记住使用完后要关闭它
 8     connection c = connect(&d);
 9     // 使用连接
10     // 如果我们在f退出前忘记调用disconnect,就无法关闭c了
11 }
时间: 2024-08-28 22:34:47

动态内存与智能指针的相关文章

C++ Primer笔记8_动态内存_智能指针

1.动态内存 C++中,动态内存管理是通过一对运算符完成的:new和delete.C语言中通过malloc与free函数来实现先动态内存的分配与释放.C++中new与delete的实现其实会调用malloc与free. new分配: 分配变量空间: int *a = new int; // 不初始化 int *b = new int(10); //初始化为10 string *str = new string(10, ); 分配数组空间: int *arr = new int[10];//分配的

【足迹C++primer】39、动态内存与智能指针(3)

动态内存与智能指针(3) /** * 功能:动态内存与智能指针 * 时间:2014年7月8日15:33:58 * 作者:cutter_point */ #include<iostream> #include<vector> #include<memory> #include<string> using namespace std; /** 智能指针和异常 */ void f() { shared_ptr<int> sp(new int(42));

【足迹C++primer】39、动态内存与智能指针(2)

动态内存与智能指针(2) 直接管理内存 void fun1() { //此new表达式在自由空间构造一个int型对象,并返回指向该对象的指针 int *pi1=new int; //pi指向一个动态分配.未初始化的无名对象 string *ps3=new string; //初始化为空string int *pi2=new int; //pi指向一个未初始化的int int *pi3=new int(1024); //pi指向的对象的值为1024 string *ps4=new string(1

C++笔记(12):动态内存和智能指针

动态内存和智能指针 动态内存: 1.针对堆里面存放的对象 2.使用new delete运算符 3.智能指针:shared_ptr(多个指针指向同一个对象);  unique_ptr(一个指针指向一个对象);     weak_ptr(弱引用,管理shared_ptr) 4.标准库函数:make_shared<int>()

C++primer第十二章读书笔记---动态内存与智能指针

    目前为止我们使用过的静态内存,栈内存和内存池,静态内存用来保存局部static对象.类static成员,以及定义在任何函数之外的成员.栈内存用来保存定义在函数内部的非static成员,分配在静态 内存或栈内存中的对象由编译器自动创建或销毁,对于栈对象仅在其定义的程序块运行时才有效,static对象在程序运行之前分配,程序结束时销毁.除了静态内存和栈内存外,每个程序还拥有一个内存池(堆)在堆上分配动态对象,当动态对象不再使用时,我们必须显示的销毁它.     (一).动态内存与智能指针  

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

静态内存用来保存局部 static 对象.类 static 数据成员 以及任何定义在函数之外的变量.栈内存用来存储定义在函数内部的非 static 对象.分配在静态或栈内存中的对象由编译器自动创建和销毁.对于栈对象,仅在其定义的程序块运行时才存在:static 对象在使用之前分配,在程序结束时销毁. 除了静态内存和栈内存,每个程序还拥有一个内存池.这部分内存被称作自由空间或堆.程序用堆来存储动态分配的对象--即,那些在程序运行时分配的对象.动态对象的生存周期由程序来控制,即当动态对象不再使用时,

第十二章 动态内存与智能指针

动态内存与智能指针 [智能指针]头文件#include<memory>shared_ptr: 允许多个指针指向同一个对象unique_ptr: "独占"所指向的对象weak_ptr:伴随类,它是一种弱引用,指向shared_ptr所管理的对象. 原文地址:https://www.cnblogs.com/sunbines/p/8552298.html

动态内存——动态内存与智能指针

全局对象在程序启动时分配,在程序结束时销毁.对于局部自动对象,当我们进入其定义所在的程序块时被创建,在离开块时销毁.局部static对象在第一次使用前分配,在程序结束时销毁. 除了自动和static对象外,C++还支持动态分配对象.动态分配的对象的生命期与它们在哪里创建是无关的,只有当显示地被释放时,这些对象才会销毁. 静态内存用来保存局部static对象.类static数据成员以及定义在任何函数之外的变量.栈内存用来保存定义在函数内的非static对象.分配在静态或栈内存中的对象由编译器自动创

动态内存和智能指针

c++中动态内存的管理是通过一对运算符来完成的:new.在动态内存中为对象分配空间并返回一个指向该对象的指针,我们呢,可以选择对对象进行初始化,delete接受一个动态对象的指针,销毁该对象,并返回与之关联的内存. 动态内存的使用的不足: 1.保证在正que的时间释放内存是及其困难 2.有时我们会忘记释放内存,这样就导致内存泄漏 3.有时还存在引用内存的情况,但是我们已经释放了它,这样就导致非法内存的指针 这样就产生了智能指针(smart pointer)来动态管理内存,智能指针优点: 1.行为

12.1 动态内存与智能指针

//练习12.10-11 shared_ptr<int> p(new int(42)); //process(p); //process(shared_ptr<int>(p)); //用p来初始化创建一个临时的智能指针 //也指向p所指向的内存 process(shared_ptr<int>(p.get())); //用p.get()返回的内置指针来初始化创建一个 //临时的智能指针,指向p所指向的内存 //当离开process函数的作用域时,这个临时智能指针会被销毁