小言_互联网的博客

(未完)第十二章:动态内存

299人阅读  评论(0)

12.1 动态内存与智能指针

12.1.1 shared_ptr类


  
  1. shared_ptr<string>p1; //可以指向string
  2. shared_ptr<list< int>>p2 //可以指向int的list

  
  1. //如果p1不为空,检查它是否指向一个空string
  2. if(p1&&pq-> empty())
  3. *p1 = "hi"; //如果p1指向一个空string,解引用p1,将一个新至赋予string

 shared_ptr和unique_ptr都支持的操作见表12.1

make_shared函数


  
  1. //指向一个值为42的int的shared_ptr
  2. shared_ptr< int> p3 = make_shared< int>( 42);
  3. //指向一个值为9999999999的string
  4. shared_ptr<string> p4 = make_shared<string>( 10, '9');
  5. //指向一个值初始化的int,即,值为0
  6. shared_ptr< int> p5 = make_shared< int>();
  7. //指向一个动态分配的空vector<string>
  8. auto p6 = make_shared<vector<string>>();

shared_ptr的拷贝和赋值 


  
  1. auto r = make_shared< int>( 42); //r指向的int只有一个引用者
  2. r = q; //给r赋值,令它指向另一个地址
  3. //递增q指向的对象的引用计数
  4. //递减r原来指向的对象的引用计数
  5. //r原来指向的对象已没有引用者,会自动释放

shared_ptr自动销毁所管理的对象

略(析构函数)

......shared_ptr还会自动释放相关联的内存


  
  1. //factory返回一个shared_ptr,指向一个动态分配的对象
  2. shared_ptr<Foo> factory(T arg)
  3. {
  4. //恰当处理arg
  5. //shared_ptr负责释放内存
  6. return make_shared<Foo>(arg);
  7. }
  8. //下面函数将factory返回的shared_ptr保存在局部变量中
  9. void use_factory(T arg)
  10. {
  11. shared_ptr<Foo> p = factory(arg);
  12. //使用o
  13. } //p离开了作用域,它指向的内存会被自动释放
  14. //但如果有如果有其他shared_ptr也指向这块内存,它就不会被释放掉
  15. shared_ptr<Foo> use_factory(T arg)
  16. {
  17. shared_ptr<Foo> p = factory(arg);
  18. //使用p
  19. return p; //当我们返回P时,引用计数进行了递增操作
  20. } //p离开了作用域,但它指向的内存不会被释放掉

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


  
  1. //当我们拷贝一个vector时,原vector和副本vector中的元素是相互分离的
  2. vector<string>v1; //空vector
  3. {
  4. vector<string>v2 = { "a", "an", "the"};
  5. v1 = v2; //从v2拷贝元素到v1中
  6. } //v2被销毁,其中的元素也被销毁,v1有三个元素,是原来v2中元素的拷贝
  7. //一般而言,如果两个对象共享底层的数据,某个对象被销毁时,我们不能单方面地销毁底层数据
  8. Blob<string> b1; //空Blob
  9. {
  10. //新作用域
  11. Blob<string> b2 = { "a", "an", "the"};
  12. b1 = b2; //b1和b2共享相同地元素
  13. } //b2被销毁了,但b2中的元素不能销毁
  14. //b1指向最初由b2创建的元素

定义StrBlob类


  
  1. class StrBlob
  2. {
  3. public:
  4. typedef std::vector<std::string>::size_type size_type;
  5. StrBlob();
  6. StrBlob(std::initializer_list<std::string>il);
  7. size_type size() const { return data-> size();}
  8. bool empty() const { return data-> empty();}
  9. //添加和删除元素
  10. void push_back(const std::string &t) { data-> push_back(t);}
  11. void pop_back();
  12. //元素访问
  13. std::string& front();
  14. std::string& back();
  15. private:
  16. std::shared_ptr<std::vector<std::string>>data;
  17. //如果data[i]不合法,抛出一个异常
  18. void check(size_type i,const std::string &msg) const;
  19. };
  20. //该类中有一个默认构造函数和一个构造函数,接受单一的initializer_list<string>类型参数
  21. //此构造函数可以接受一个初始化器的花括号列表

StrBlob构造函数


  
  1. StrBlob:: StrBlob(): data(make_shared<vector<string>>()){ }
  2. StrBlob:: StrBlob(initializer_list<string>il):
  3. data(make_shared<vector<srting>>(il)){ }

元素访问成员函数


  
  1. //check函数检查一个给定索引是否在合法范围内
  2. //check函数还会接受一个string参数,它会将此参数传递给异常处理程序,该string描述了错误内容
  3. void StrBlob::check(size_type i,const string &msg)const
  4. {
  5. if(i>=data-> size())
  6. throw out_of_range(msg);
  7. }
  8. //pop_back和元素访问函数首先调用check,如果check成功,这些成员函数继续利用底层vector的操作来完成自己的工作
  9. string& StrBlob::front()
  10. {
  11. //如果vector为空,check会抛出一个异常
  12. check( 0, " front on empty StrBlob");
  13. return data-> front();
  14. }
  15. string& StrBlob::back()
  16. {
  17. check( 0, "back on empty StrBlob");
  18. return data-> back();
  19. }
  20. void StrBlob::pop_back()
  21. {
  22. check( 0, "pop_back on empty StrBlob");
  23. data-> pop_back();
  24. }

StrBlob的拷贝,赋值和销毁

略(详见书本)

12.1.2直接内存管理

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


  
  1. int *pi = new int; //pi指向一个动态分配的、未初始化的无名对象
  2. string *ps = new string; //初始化为空string
  3. int *pi = new int; // pi指向一个未初始化的int
  4. int *pi = new int( 1024); //pi指向的对象的值为1024
  5. string *ps = new string( 10, '9'); //ps为9999999999
  6. //vector有10个元素,值依次从0到9
  7. vector< int> *pv = new vector< int>{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
  8. string *ps1 = new string; //默认初始化为空string
  9. string *ps = new string(); //值初始化啊为空string
  10. int *pi1 = new int; //默认初始化:*pi1的值未定义
  11. int *pi2 = new int(); //值初始化为0;*pi2为0
  12. //若提供一个括号包围的初始化器,就可以使用auto。从此初始化器来推断我们想要分配的对象的类型。
  13. //但是,由于编译器要用初始化器的类型来推断要分配的类型,只有当括号中仅有单一初始化器时才可使用auto
  14. auto p1 = new auto(obj); //p指向一个与obj类型相同的对象
  15. //该对象用obj进行初始化
  16. auto p2 = new auto{a,b,c}; //错误:括号中只能有蛋哥初始化器

动态分配的const对象


  
  1. //用new分配const对象是合法的
  2. //分配并初始化一个const int
  3. const int *pci = new const int( 1024);
  4. //分配并默认初始化一个const的空string
  5. const string *pcs = new const string;
  6. //对于一个定义了默认构造函数的类类型,其const动态对象可以隐式初始化,而其他类型的对象就必须显示初始化

内存耗尽(定位new的使用)


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

释放动态内存


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

指针值和delete


  
  1. int i,*pi1 = &i,*pi2 = nullptr;
  2. double *pd = new double( 33),*pd2 = pd;
  3. delete i; //错误,i不是一个指针
  4. delete pi1; //未定义:pi1指向一个局部变量
  5. delete pd; //正确
  6. delete pd2; //未定义:pd2指向的内存已经被释放了
  7. delete pi2; //正确:释放一个空指针总是没有错误的
  8. //虽然一个const对象的值不能被改变,但其本身是可以被销毁的。如同其他动态对象一样,想要释放一个const
  9. //动态内存对象,只要delete指向它的指针即可
  10. const itn *pci = new const int( 1024);
  11. delete pci; //正确:释放一个const对象

动态对象的生存期直到被释放时为止


  
  1. //factory返回一个指针,指向一个动态分配的对象
  2. Foo* factory(T arg)
  3. {
  4. //看情况处理arg
  5. return new Foo(arg); //调用者负责释放此内存
  6. }
  7. //下面函数调用factoty,后者分配一个类型为Foo的新对象。当use_factory返回时,局部变量p被销毁
  8. //此变量是一个内置指针,而而不是一个智能指针
  9. void use_factory(T arg)
  10. {
  11. Foo *p = factory(arg);
  12. //使用p但不delete它
  13. } //p离开了它的作用域,但它所指向的内存没有被释放
  14. //下面函数中p时指向factory分配的内存的唯一指针,一旦use_factory返回,程序就没有办法释放这块内存了。
  15. //根据整个程序的逻辑,修正这个错误的正确方式是在use_factory中记得释放内存
  16. void use_factory(T arg)
  17. {
  18. Foo *p = factory(arg);
  19. //使用p
  20. delete p; //现在记得释放内存,我们已经不需要它了
  21. }
  22. //还有有一种可能,我们的系统中的其他代码要使用use_factory所分配的对象,我们就应该修改此函数
  23. //让其返回以一个指针,指向它分配的内存
  24. Foo* use_factory(T arg)
  25. {
  26. Foo *p = factory(arg);
  27. //使用p
  28. return p; //调用者必须释放内存
  29. }

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

1.忘记delete内存

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

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

delete之后重置指针值......

空悬指针的概念及避免空悬指针的方式:在指针即将要离开其作用域之前释放掉它所关联的内存

......这只是提供了有限的保护


  
  1. int *p(new int(42)); //p指向动态内存
  2. auto q = p; //p和q指向相同的内存
  3. delete p; //p和q均变为无效
  4. p = nullptr //指出p不再绑定到任何对象

12.1.3 shared_ptr和new结合使用


  
  1. //new返回的指针来初始化智能指针
  2. shared_ptr< double>p1; //shared_ptr可以指向一个double
  3. shared_ptr< int> p2( new int( 42)); //p2指向一个值为42的int
  4. //接受指针参数的智能指针构造函数时explicit的,因此不能将一个内置指针隐式转化为一个
  5. //智能指针,必须使用直接初始化形式来初始化一个智能指针
  6. shared_ptr< int>p1 = new int( 1024); //错误:必须使用直接初始化形式
  7. shared_ptr< int> p2( new int( 1024)); //正确:使用了直接初始化形式
  8. //由于不能进行内置指针到智能指针之间的隐式转换,一个返回shared_ptr的函数不能在
  9. //其返回语句中隐式转换一个普通指针
  10. shared_ptr<int> clone(int p)
  11. {
  12. return new int(p); //错误:隐式转换为shared_ptr<int>
  13. }
  14. //我们必须将shared_ptr显示绑定到一个想要返回的指针上
  15. shared_ptr< int> clone( int p)
  16. {
  17. //正确:显示地用int*创建shared_ptr<int>
  18. return shared_ptr< int>( new int(p));
  19. }

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


  
  1. //考虑下面对shared_ptr进行操作地函数
  2. //在函数被调用时ptr被创建并初始化
  3. void process(shared_ptr<int>ptr)
  4. {
  5. //使用ptr
  6. } //ptr离开作用域,被销毁
  7. //此函数的正确方法是传递给它一个shared_ptr
  8. shared_ptr< int> p( new int( 42)); //引用计数为1
  9. process(p); //拷贝p会递增它的引用计数;在process中引用计数值为2
  10. int i = *p; //正确:引用计数为1
  11. int *x(new int(1024)); //危险:x是一个普通指针,不是一个智能指针
  12. process(x); //错误:不能将int*转换为一个shared_ptr<int>
  13. process( shared_ptr< int>(x)); //合法的,但内存会被释放
  14. int j = *x; //未定义的:x是一个空悬指针

使用一个内置指针来访问一个智能指针所负责的对象是很危险的,因为我们无法知道对象何时会被销毁

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


  
  1. shared_ptr< int> p( new int( 42)); //引用计数为1
  2. int *q = p. get(); //正确:但使用q时要注意,不要让它管理的指针被释放
  3. {
  4. //新程序块
  5. //未定义:两个独立的shared_ptr指向相同的内存
  6. shared_ptr< int>(q);
  7. } //程序块结束,q被销毁,它指向的内存被释放
  8. int foo = *p; //未定义;p指向的内存已经被释放了

其他shared_ptr操作


  
  1. //可以用reset来将一个新的指针赋予一个shared_ptr
  2. p = new int( 1024); //错误:不能将一个指针赋予shared_ptr
  3. p. reset( new int( 1024)); //正确:p指向一个新对象
  4. if(!p. unique())
  5. p. reset( new string(*p)); //我们不是唯一用户;分配新的拷贝
  6. *p += newVal; //现在我们知道自己是唯一的用户,可以改变对象的值

12.1.4 智能指针和异常


  
  1. //如果使用智能指针。即使程序过早结束,智能指针类也能确保在内存不再需要是将其释放
  2. void f()
  3. {
  4. shared_ptr<int> sp(new int(42)); // 分配一个新对象
  5. //这段代码抛出一个异常,且在f中为未被捕获
  6. } //这段函数结束时shared_ptr自动释放内存
  7. //如果使用内置指针管理内存,且在new之后对应的delete之前发生了异常,则内存不会被释放
  8. void f()
  9. {
  10. int *ip = new int( 42); //动态分配一个新对象
  11. //这段代码抛出一个异常,且在f中未被捕获
  12. delete ip; //在退出 之前释放内存
  13. }

智能指针和哑类


  
  1. //假定我们正在使用一个C和C++都使用的网络库,使用这个库的代码可能是这样的
  2. struct destination; //表示我们正在链接什么
  3. struct connection; //使用链接所需的信息
  4. connection connect(destination*); //打开链接
  5. void disconnect(connection); //关闭给定的链接
  6. void f(destination &d/*其他参数*/)
  7. {
  8. //获得一个链接;记住使用完后要关闭它
  9. connection c = connect(&d);
  10. //使用链接
  11. //如果我们在f退出前忘记调用disconnect,就无法关闭c了
  12. }

使用我们自己的释放操作


  
  1. //为了用shared_ptr来管理一个connection 我们必须首先定义一个函数来代替delete
  2. //这个删除器函数必须能够完成对shared_ptr中保存的指针进行释放的操作
  3. //在本例中,我们的删除器必须接受单个类型为connection*的参数
  4. void end_connection(connection *p){ disconnect(*p);}
  5. //当我们创建一个shared_ptr时,可以传递一个(可选的)指向删除器函数的参数
  6. void f(destination &d/*其他参数*/)
  7. {
  8. connection c = connect(&d);
  9. shared_ptr<connection> p(&c,end_connection);
  10. //使用链接
  11. //当f退出时(即使是由于异常而退出),connection会被正确关闭
  12. }

12.1.5unique_ptr

12.2 动态数组

12.3 使用标准库:文本查询程序

小结


转载:https://blog.csdn.net/m0_61548909/article/details/125610578
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场