一、动态内存

动态内存所在的位置在堆区,由程序员手动分配并手动释放,而不像栈内存由系统分配和自动释放

C++通过new运算符为对象在堆上分配内存空间并返回该对象的地址,并用delete运算符销毁对象并释放对象所占的动态内存,如果分配动态内存后没有手动释放会产生内存泄露

new一个对象时分为三步:1、调用operator new或operator new[]来分配内存空间。分配内存空间这一步,实质上调用的是malloc。2、编译器调用构造函数构造对象。3、构造完成后,返回一个指向对象的指针

delete时,分为两步:调用指针所指对象的析构函数销毁对象。2、调用operator delete或者operator delete[]释放内存空间,回收内存空间这一步,实质上也是调用的是free

示例

void newexample()
{int *pi=new int;string *ps=new string;cout<<*pi<<","<<*ps<<endl;int *pi1=new int();string *ps1=new string(10, 's');cout<<*pi1<<","<<*ps1<<endl;vector<int> *pv=new vector<int>{10, 20, 30};cout<<(*pv)[0]<<endl;
}

上面的代码说明了如何用new动态分配内存并创建对象,如果new后边的类型名后边没有括号,那么将创建一个默认初始化的对象,如果new后边的类型名有大括号,则创建了一个列表初始化的对象,如果new后边的类型名带有小括号,则是按照构造函数的形式进行直接初始化

但是上边的代码将产生内存泄漏,因为pi,ps,pi1,ps1,pv都是局部变量,当函数执行结束后,局部变量(这些指针)将被自动释放,所以,之后再也没有任何指针能指向并释放new创建的对象的内存,也就造成了内存泄露,解决办法就是在函数退出之前使用delete运算符释放指针所指向的对象内存

delete pi, pi1, ps, ps1, pv;

如果函数返回一个指向动态内存的指针,那么接收该指针的拷贝的对象要负责释放指向的内存

指针被delete后,指针变为空悬指针(指向内存中的数据已经无效),避免空悬指针的办法就是delete指针后将指针置为空,这样在使用前对指针判空就能知道指针不可以再解引用。但是这个办法也是有限的

示例

void danglingpointer()
{string *pa= new string("1234");string *pb=pa;delete pb;pb=nullptr;if (pa!=nullptr) {cout<<*pa<<endl;}
}

上述代码中pa和pb都指向同一块内存,将pb指针delete后,pa指向的数据也被释放,然后将pb指向空,但是pa此时仍然指向原先的内存,不是空指针,对pa解引用之后,程序出现段错误

new也可以动态分配const对象,因为const对象创建时必须初始化,所以new时,也必须初始化,因为是const对象,所以返回的是一个指向const对象的指针

const int *pc=new const int(1024); 

new除了可已创建单个对象,还可以创建对象数组,只需要在对象类型后面加方括号

示例

void newarray()
{int *p1=new int[10];int *p2=new int[10]{1,2,3,4,5};string *p3=new string[10];string *p4=new string[10]();
}

其中,第三行和第五行对10个元素进行默认初始化,第四行是列表初始化,第六行是值初始化

上面的代码也是存在内存泄露的,只不过在用delete释放资源时,语法不同

 delete []p1;delete []p2;delete []p3;delete []p4;

需要在delete和指针之间加上方括号

当用new创建数组时,方括号中的数字可以小于0,知道有这么个东西就行,实际写代码不要这样写

此外,无论是用new创建单个对象还是对象的数组,返回的都是对象类型的指针,C/C++中,指针并不知道指向的是单个对象还是对象的数组

二、智能指针

当使用局部变量的指针指向new创建的对象时,容易产生内存泄露,而应对措施有限,所以为了防止内存泄露,C++设计了三个智能指针:share_ptr, weak_ptr,  unique_ptr,使用这三个智能指针之前,要#include<memory>

2.1 shared_ptr

shared_ptr是C++标准库中的模板,允许多个shared_ptr对象同时指向一个对象,内部通过引用计数得到有多少个shared_ptr对象指向该对象,当最后一个shared_ptr对象被销毁时,被指向的对象会自动销毁,将所占的内存释放。

2.1.1 shared_ptr的常用构造函数

constexpr shared_ptr() noexcept;
constexpr shared_ptr(nullptr_t) : shared_ptr() {}template <class U> explicit shared_ptr (U* p);
template <class U, class D> shared_ptr (U* p, D del);
template <class D> shared_ptr (nullptr_t p, D del);shared_ptr (const shared_ptr& x) noexcept;
template <class U> shared_ptr (const shared_ptr<U>& x) noexcept;template <class U> explicit shared_ptr (const weak_ptr<U>& x);
template <class U, class D> shared_ptr (unique_ptr<U,D>&& x);

示例

void shareptr()
{shared_ptr<int> spi;//默认构造shared_ptr<int> spn(nullptr);//空指针构造shared_ptr<int> spi1(new int(1));//第三个构造shared_ptr<int> spi2(new int[10], default_delete<int>());//第四个构造函数shared_ptr<int> spi3=spi1;//(spi1), 拷贝构造函数weak_ptr<int> wpi3=spi3;shared_ptr<int> spi4(wpi3);//通过weak_ptr初始化的构造函数shared_ptr<int> spi5(unique_ptr<int>(new int));//通过unique_ptr初始化的构造函数cout<<spi.use_count()<<","<<spn.use_count()<<","<<spi1.use_count()<<","<<spi2.use_count()<<","<<spi3.use_count()<<","<<spi4.use_count()<<","<<spi5.use_count()<<","<<wpi3.use_count()<<endl;
}

上面的代码中default_delete是一个函数对象类,返回一个函数对象作为删除器,删除器也可以自己定义,当智能指针指向一个普通对象时,通过default_delete<T>()得到的函数对象会调用delete,当智能指针指向一个对象的数组时,通过default_delete<T[]>()得到的函数对象会调用delete[]

use_count成员函数返回共享对象的智能指针的个数,其中spi1,spi3和spi4共享new int(1)

2.1.2 shared_ptr的常用操作

explicit operator bool() const noexcept;
element_type& operator*() const noexcept;
element_type* operator->() const noexcept;

这三个操作说明可以像普通指针一样使用智能指针判空和解引用

示例

void useshareptr()
{shared_ptr<vector<int>> vsp(new vector<int>(10,1));if (vsp) {cout<<vsp->back()<<endl;cout<<(*vsp).front()<<endl;}
}

除了上述和普通指针一样的操作常规外,还有一些其他操作如下

element_type* get() const noexcept;//返回智能指针中保存的指针void reset() noexcept;//释放智能指针原来指向的对象
template <class U> void reset (U* p);//释放智能指针原来指向的对象并重新初始化智能指针
template <class U, class D> void reset (U* p, D del);//释放智能指针原来指向的对象并重新初始化智能指针,并令智能指针的删除器为delvoid swap (shared_ptr& x) noexcept;//交换两个智能指针
bool unique() const noexcept;//等价于sp.use_count()==1

除了上述操作外,还有一个非成员函数make_shared,返回的是一个shared_ptr,通常用于初始化智能指针,使用make_shared时,make_shared的参数必须与模板参数类型的构造函数相匹配

template <class T, class... Args>
shared_ptr<T> make_shared (Args&&... args);

示例

void shareptrnewope()
{shared_ptr<int> spi=make_shared<int>(10);spi.reset();int *psp=spi.get();cout<<(psp==nullptr)<<endl;spi.reset(new int(20), default_delete<int>());cout<<*spi<<endl;
}

2.2 weak_ptr

weak_ptr也是一种智能指针,一般会和shared_ptr绑定在一起,但是,当用weak_ptr和shared_ptr绑定时,并不能增加shared_ptr的引用计数(也就是说,weak_ptr并不延长或缩短对象的生命周期),当一个对象的最后一个shared_ptr被销毁后,weak_ptr指向的对象也会被释放。可以说,weak_ptr是shared_ptr的附属品

2.2.1 weak_ptr的构造函数

constexpr weak_ptr() noexcept;//默认构造函数weak_ptr (const weak_ptr& x) noexcept;//拷贝构造函数
template <class U>
weak_ptr (const weak_ptr<U>& x) noexcept;
template <class U>
weak_ptr (const shared_ptr<U>& x) noexcept;

构造函数没有删除器参数,是因为weak_ptr不能控制对象的生命周期

示例

void weakptr()
{weak_ptr<int> wp1;weak_ptr<int> wp2(wp1);shared_ptr<int> sp=make_shared<int>(10);wp2=sp;cout<<wp1.use_count()<<endl;cout<<wp2.use_count()<<endl;
}

上述代码也说明,weak_ptr只有绑定shared_ptr,才能增加自身的引用计数

2.2.2 weak_ptr的常用操作

void reset() noexcept;//将weak_ptr置为空
void swap (weak_ptr& x) noexcept;
long int use_count() const noexcept;
bool expired() const noexcept;//当use_count()==0,返回true,否则返回false
shared_ptr<element_type> lock() const noexcept;//如果expired()返回true,那么lock返回一个空的shared_ptr,
否则返回一个指向weak_ptr对象的shared_ptr

因为weak_ptr指向的对象的生命周期受shared_ptr影响,当使用weak_ptr时,其指向的对象可能已经不存在,所以,weak_ptr不支持解引用操作和箭头操作,只能用lock获取shared_ptr,从而操作对象

示例

void weakptrope()
{shared_ptr<int> sp=make_shared<int>(10);weak_ptr<int> wp(sp);wp.reset();cout<<sp.use_count()<<endl;cout<<wp.use_count()<<endl;cout<<*sp<<endl;cout<<wp.expired()<<endl;shared_ptr<int> sp2=wp.lock();cout<<sp2.use_count()<<endl;
}

上述代码中,wp和sp都指向同一个对象,虽然wp被重置,但是并不影响sp的生命周期,只是wp自身被置空而已

2.3 unique_ptr

unique_ptr也是一个能防止内存泄漏的智能指针,这点和shared_ptr相同,和shared_ptr不同的是,unique_ptr所指向的对象在只能被一个unique_ptr所占有,也就是说unique_ptr独享指向的对象,一旦一个unique_ptr被销毁或者指向其他对象,先前指向的对象也会被释放。此外,unique_ptr还可以指向数组,这点和shared_ptr不同,shared_ptr只能指向单个对象

2.3.1 unique_ptr的构造函数和赋值

template <class T, class D = default_delete<T>> class unique_ptr;
template <class T, class D> class unique_ptr<T[],D>;//与数组绑定的unique_ptr的声明constexpr unique_ptr() noexcept;
constexpr unique_ptr (nullptr_t) noexcept : unique_ptr() {}
explicit unique_ptr (pointer p) noexcept;
unique_ptr (pointer p, typename conditional<is_reference<D>::value,D,const D&> del) noexcept;unique_ptr (const unique_ptr&) = delete;//拷贝和赋值都是delete,拷贝元素和赋值运算被禁用,所以unique_ptr不支持拷贝和赋值运算
unique_ptr& operator= (const unique_ptr&) = delete;

虽然unique_ptr不支持拷贝和赋值操作,但是还是有办法将一个unique_ptr的所占有的资源转移给别的unique_ptr,那就是使用std::move

template <class T>
typename remove_reference<T>::type&& move (T&& arg) noexcept;

示例

void uniqueptrinit()
{unique_ptr<int> up1;unique_ptr<int> up2(nullptr);unique_ptr<int> up3(new int(100),default_delete<int>());unique_ptr<int> up4(move(up3));cout<<(up3==nullptr)<<endl;cout<<*up4<<endl;
}

可见,当使用move后,up3被销毁,up4接收了原来up3所指向的对象,因为是智能指针,所以,当函数结束后,智能指针被自动销毁,指向的对象也被销毁

2.3.2 unique_ptr的常用操作

pointer get() const noexcept;//返回unique_ptr保存的指针,意义同shared_ptr
pointer release() noexcept;//将指向对象的指针返回并将unique_ptr对象置为空
void reset (pointer p = pointer()) noexcept;//释放unique_ptr原来指向的对象并接受新对象的指针p
void swap (unique_ptr& x) noexcept;

和make_shared类似同样有make_unique,同样,make_unique的参数必须与模板参数类型的构造函数相匹配

template< class T, class... Args >
unique_ptr<T> make_unique( Args&&... args );//创建指向单个对象的unique_ptrtemplate< class T >
unique_ptr<T> make_unique( std::size_t size );//创建指向数组对象的unique_ptr

如果一个unique_ptr指向单个对象,和shared_ptr一样,支持bool、解引用和箭头操作,如果unique_ptr指向一个数组,那么还支持下标操作

explicit operator bool() const noexcept;
typename add_lvalue_reference<element_type>::type operator*() const;
pointer operator->() const noexcept;
element_type& operator[](size_t i) const; 

示例

void uniqueope()
{unique_ptr<list<int>> ulp=make_unique<list<int>>(10,6);list<int> *lp=ulp.get();ulp->insert(lp->begin(), 1);cout<<*ulp->begin()<<endl;ulp.reset(new list<int>{1,2,3,4,5});list<int> *lp2=ulp.release();cout<<(ulp==nullptr)<<endl;unique_ptr<int[]> up=make_unique<int[]>(10);up[0]=10;ulp.reset();//等价于reset(nullptr)up.reset(nullptr);
}

参考

《C++ Primer》

《C++标准库》

http://www.cplusplus.com/reference/memory/shared_ptr/

http://www.cplusplus.com/reference/memory/weak_ptr/

http://www.cplusplus.com/reference/memory/unique_ptr/

https://zh.cppreference.com/w/cpp/memory/unique_ptr/make_unique

欢迎大家评论交流,作者水平有限,如有错误,欢迎指出

C++知识点34——动态内存与智能指针相关推荐

  1. 2.5w字长文爆肝 C++动态内存与智能指针一篇搞懂!太顶了!!!

    动态内存与智能指针 1.动态内存与智能指针 2.shared_ptr类 2.1.make_shared函数 2.2.shared_ptr的拷贝和赋值 2.3.shared_ptr自动销毁所管理的对象 ...

  2. 【C++ Primer 第5版 笔记】第12章 动态内存与智能指针

    转载:http://blog.csdn.net/wwh578867817/article/details/41866315 第 12 章 动态内存 与 智能指针 静态内存 用来保存:(1)局部stat ...

  3. C++ Primer 5th笔记(chap 12 动态内存)智能指针概述

    1. 对象的生存期 内存类型 定义 分配和销毁时机 全局对象 程序启动时分配,程序结束时销毁 静态内存 局部static对象类static数据成员 第一次使用时分配,程序结束时销毁 栈内存 定义在函数 ...

  4. C++相关:动态内存和智能指针

    前言 在C++中,动态内存的管理是通过运算符new和delete来完成的.但使用动态内存很容易出现问题,因为确保在正确的时间释放内存是及其困难的.有时候我们会忘记内存的的释放,这种情况下就会产生内存泄 ...

  5. 【Smart_Point】动态内存与智能指针

    动态内存 动态内存使用的三种原因 程序不知道自己需要多少对象 程序不知道所需对象的准确类型 程序需要在多个对线之间共享数据 文章目录 动态内存 动态内存使用的三种原因 实例1: Exercise 12 ...

  6. 12.1 动态内存与智能指针(2)

    今天继续学习12.1节时,从练习12.17中发现了一个问题. 首先摘录教材中的原话--一个unique_ptr"拥有"它所指向的对象.与shared_ptr不同,某个时刻只能有一个 ...

  7. 【Smart_Point】动态内存与智能指针实战:文本查询程序(设计set,map,智能指针的应用)

    文章目录 Cpp读入结构性数组 文本查询程序 文本查询程序本版1 Cpp读入结构性数组 #include<sstream> #include<iostream> #includ ...

  8. 【C++ 语言】智能指针 引入 ( 内存泄漏 | 智能指针简介 | 简单示例 )

    文章目录 I . 智能指针 引入 II . 智能指针 简介 III . 智能指针 简单示例 I . 智能指针 引入 1 . 示例前提 : 定义一个 Student 类 , 之后将该类对象作为智能指针指 ...

  9. 道高一尺 魔高一丈 内存泄漏智能指针

    一.什么是内存泄漏 内存泄漏是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果. 内存泄漏主要分为两类: 1. 堆内存泄漏 堆 ...

最新文章

  1. redis主从复制故障转移
  2. C# 获取进程或线程的信息
  3. 推荐系统相关算法(2):k-nearest neighbor
  4. [html] 说说你对Media Source Extensions(MSE)的理解,它解决了什么问题呢?
  5. 离线安装pytorch-gpu
  6. git reflog
  7. 2021-2025年中国丁基胶粘剂行业市场供需与战略研究报告
  8. ======第一章总结及习题======
  9. mysql execute 存储过程_Mysql存储过程调用
  10. Java验证导入excel格式
  11. 用Python修改Minecraft的mod
  12. 怎样测试手机性能软件,如何检测手机性能的软件
  13. 【Opencv实战】纯手工代码打造车牌检测程序,秒变智能检测你值得拥有~(附源码)
  14. 计算机顶会论文多少钱,计算机视觉顶会文章的解读汇总(CVPR/ECCV/ICCV/NIPS)
  15. 中国境内哪个chatGPT最好用
  16. HTTP协议的理解和使用
  17. Actions as Moving Points复现
  18. 亿级流量电商详情页系统实战-25.亿级流量商品详情页的多级缓存架构介绍
  19. 蜗牛星际风扇噪音的简单解决方案
  20. [转载] 史记·马加爵列传

热门文章

  1. 我所知道的flex布局 —— 上篇
  2. 一般计算 【2007】三3 C++版
  3. getReadableDatabase与getWritableDatabase的区别
  4. selenium借助AutoIt识别上传(下载)详解
  5. 终于知道10月27-28-29这3天为什么调整了
  6. Java技术专题之JVM你的内存泄露了吗?
  7. iPhone地图 实战iPhone GPS定位系统
  8. DNS MX记录一定要放在A记录之前
  9. 每日算法练习之李白喝酒问题
  10. MapTask的工作机制