动态内存与智能指针

来源:互联网 发布:数据库镜像同步 编辑:程序博客网 时间:2024/05/17 12:03

我们先来看一些对象的生存期。全局对象在程序启动时分配,在程序结束时销毁。局部static对象在第一次使用前分配,在程序结束时销毁。局部自动对象,在进入其定义所在的程序块儿时被创建,离开块时销毁。即,它们都是由编译器自动创建与销毁。
而动态分配的对象的生存期与它们在哪里创建的无关,只有当显式地被释放时,这些对象才销毁。

在C++中,动态内存的管理是通过一对运算符来完成的:

new:在动态内存为对象分配空间并返回一个指向该对象的指针,可以选择对对象进行初始化。delete:接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。

动态内存的使用很容易出问题,这里列举如下几点:

缓冲区溢出空悬指针/野指针重复释放: 释放已经被释放过了的内存内存泄漏: 忘记释放内存不配对的new[]/delete

先列这几条,等先了解智能指针的概念以后在来看怎么用智能指针解决这些问题。

为了更安全(同时也更容易)地使用动态内存,标准库提供了智能指针类型来管理动态对象。智能指针的行为类似于常规指针,重要的区别是它负责自动释放所指向的对象。

我们首先来看一下shared_ptr类,该类型在memory头文件中。

shared_ptr类

智能指针也是模板。因此在创建一个智能指针时,必须提供它可以指向的类型。

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

默认初始化的智能指针保存着一个空指针。类似与普通指针,解引用一个智能指针返回它指向的对象。如果在一个条件判断中使用智能指针,效果是检测它是否为空。

//如果p1指向一个空string,解引用p1,将一个新值给stringif(p1 && p1->empty()){   *p1 = "hi";}

最安全的分配和使用动态内存的方法是调用标准库函数make_shared。定义在头文件memory中。该函数在动态内存中分配一个对象并初始化,返回指向此对象的shared_ptr。make_shared也是一个模板,使用方法如下:

//指向一个值为42的int对象的shared_ptrshared_ptr<int> p3 = make_shared<int>(42);//p4指向一个"9999999999"的string对象shared_ptr<string> p4 = make_shared<string>(10,'9');//p5指向一个值初始化的int,即值为0shared_ptr<int> p5 = make_shared<int>();

make_shared用其参数来构造给定类型的对象。例如,调用make_shared时传递的参数必须与string的某个构造函数相匹配。通常用auto定义一个对象来保存make_shared的结果:

//p6指向一个动态分配的vector<string>auto p6 = make_shared<vector<string>>();

shared_ptr的拷贝和赋值

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

auto p = make_shared<int>(42);   //p指向的对象只有p一个引用者auto q(p);                       //pq指向相同的对象,此对象有两个引用者。

可以认为每个shared_ptr都有一个关联的计数器(引用计数)。无论何时,拷贝一个shared_ptr,计数器都会递增。例如:

用一个shared_ptr初始化另外一个shared_ptr将一个shared_ptr作为一个参数传递给一个函数当一个shared_ptr作为一个函数的返回值

相对的,给一个shared_ptr赋予一个新值或者shared_ptr被销毁时,计数器就会递减。
一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象。

auto r = make_shared<int>(42);r = q;  //q指向的对象的引用计数递增。        //r原来指向的对象的引用计数递减。        //r原来指向的对象没有了引用者,会自动释放。

当指向一个对象的最后一个shared_ptr被销毁时,shared_ptr类会自动销毁此对象,它是通过析构函数来完成销毁动作的。shared_ptr的析构函数会递减它所指向的对象的引用计数,如果引用计数边为0,shared_ptr的析构函数就会销毁对象,并释放它占用的内存。

对于一块内存,shared_ptr类保证只要有任何shared_ptr对象引用它,它就不会被释放掉。

但是,如果你忘记了销毁程序不需要的shared_ptr,程序仍会正确执行,但会浪费内存。一种可能的情况是,将shared_ptr放在一个容器里,当不需要某些元素时,应该确保用erase删除那些不需要的shared_ptr元素。

程序使用动态生存期的资源的类 出于以下三种原因之一:

程序不知道自己需要使用多少对象(空间)例如,容器类程序不知道所需对象的准确类型程序需要在多个对象间共享数据

下面是一个需要在多个对象间共享数据的例子:
我们定义一个strblob类,保存一组元素。与容器不同,我们希望strblob对象的不同拷贝之间共享相同的元素。我们可以用一个vector来保存元素,但是,不能在一个strblob对象内直接保存vector,因为一个对象的数据成员在对象销毁时也会被销毁。为了实现strblob对象共享相同的底层数据,我们可以将vector保存在动态内存中,然后为每个strblob设置一个shared_ptr来管理动态内存分配的vector。这个shared_ptr的成员将记录有多少个shared_ptr共享相同的vector,并在vector的最后一个使用者被销毁时释放vector。

#ifndef _STRBLOB_H#define _STRBLOB_H#include<string>#include<memory>#include<list>#include<vector>using namespace std;class strblob{public:    strblob();    strblob(std::initializer_list<std::string> s);    void print(){        for(auto v : *data_){            std::cout << v << std::endl;         }    }private:    std::shared_ptr<std::vector<std::string>> data_;};strblob::strblob():data_( make_shared<vector<string>>() ){}strblob::strblob(initializer_list<string> s):data_(make_shared<vector<string>>(s)){}#endif

下面是一个测试用例

#include"strblob.h"int main(int argc,char *argv[]){    std::initializer_list<std::string> s = {"hello"};    strblob A;       //使用不带参数的构造函数    strblob B(s);    //使用带参数的构造函数    A.print();    B.print();    std::cout << std::endl;    //B对象的shred_ptr指向的对象的引用计数递增,A的递减并且释放A的shared_ptr指向的对象    A = B;    //使用默认赋值    A.print();    B.print();    return 0;}

其他shared_ptr操作:

我们可以用reset来将一个新的指针赋予一个shared_ptr

p.reset();     //若p是唯一指向对象的shared_ptr,reset会释放此对象。p.reset(q);    //若传递了可选参数内置指针q,会令p指向q,否则会将p置为空。p.reset(q,d);  //如果还传递了参数d,将会调用d而不是delete来释放q

weak_ptr

weak_ptr是一种不控制所指向对象生存期的智能指针,它指向由一个shared_ptr管理的对象。将一个weak_ptr绑定到shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。即使有weak_ptr指向对象,对象也还是会被释放,因此weak_ptr的名字抓住了这种智能指针“弱”共享对象的特点。

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

auto p = make_shared<int>(42);weak_ptr<int> wp(p);  //wp弱共享p; p的引用计数未改变。

由于waek_ptr指向的对象可能不存在,因此我们不能直接使用weak_ptr来访问对象,而必须调用lock()。此函数检查weak_ptr指向的对象是否仍存在,如果存在,lock返回一个指向共享对象的shared_ptr。与任何其他shared_ptr类似,只要此shared_ptr存在,它所指向的底层对象也就会一直存在。

if(auto np = wp.lock()){//进入if条件证明wp指向的对象存在,在这里np与p共享对象}
原创粉丝点击