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

12.1 动态内存与智能指针

12.1.1 shared_ptr类

shared_ptr<string>p1; //可以指向string
shared_ptr<list<int>>p2 //可以指向int的list
//如果p1不为空,检查它是否指向一个空string
if(p1&&pq->empty())
 *p1 = "hi"; //如果p1指向一个空string,解引用p1,将一个新至赋予string

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

make_shared函数


 //指向一个值为42的int的shared_ptr
 shared_ptr<int> p3 = make_shared<int>(42);

 //指向一个值为9999999999的string
 shared_ptr<string> p4 = make_shared<string>(10,'9');

 //指向一个值初始化的int,即,值为0
 shared_ptr<int> p5 = make_shared<int>();

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

shared_ptr的拷贝和赋值 

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

shared_ptr自动销毁所管理的对象

略(析构函数)

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

//factory返回一个shared_ptr,指向一个动态分配的对象
shared_ptr<Foo> factory(T arg)
{
    //恰当处理arg
    //shared_ptr负责释放内存
    return make_shared<Foo>(arg);
}

//下面函数将factory返回的shared_ptr保存在局部变量中
void use_factory(T arg)
{
    shared_ptr<Foo> p = factory(arg);
    //使用o
}//p离开了作用域,它指向的内存会被自动释放

//但如果有如果有其他shared_ptr也指向这块内存,它就不会被释放掉
shared_ptr<Foo> use_factory(T arg)
{
    shared_ptr<Foo> p = factory(arg);
    //使用p
    return p;//当我们返回P时,引用计数进行了递增操作
}//p离开了作用域,但它指向的内存不会被释放掉

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

//当我们拷贝一个vector时,原vector和副本vector中的元素是相互分离的
vector<string>v1;//空vector
{
    vector<string>v2 = {"a","an","the"};
    v1 = v2;  //从v2拷贝元素到v1中
}//v2被销毁,其中的元素也被销毁,v1有三个元素,是原来v2中元素的拷贝

//一般而言,如果两个对象共享底层的数据,某个对象被销毁时,我们不能单方面地销毁底层数据
Blob<string> b1;//空Blob
{
    //新作用域
    Blob<string> b2 = {"a","an","the"};
    b1 = b2; //b1和b2共享相同地元素
}//b2被销毁了,但b2中的元素不能销毁
 //b1指向最初由b2创建的元素

定义StrBlob类

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

StrBlob构造函数


 StrBlob::StrBlob(): data(make_shared<vector<string>>()){ }
 StrBlob::StrBlob(initializer_list<string>il):
               data(make_shared<vector<srting>>(il)){ }

元素访问成员函数

//check函数检查一个给定索引是否在合法范围内
//check函数还会接受一个string参数,它会将此参数传递给异常处理程序,该string描述了错误内容
void StrBlob::check(size_type i,const string &msg)const
{
    if(i>=data->size())
       throw out_of_range(msg);
}

//pop_back和元素访问函数首先调用check,如果check成功,这些成员函数继续利用底层vector的操作来完成自己的工作
string& StrBlob::front()
{
    //如果vector为空,check会抛出一个异常
    check(0," front on empty StrBlob");
    return data->front();
}
string& StrBlob::back()
{
    check(0,"back on empty StrBlob");
    return data->back();
}
void StrBlob::pop_back()
{
    check(0,"pop_back on empty StrBlob");
    data->pop_back();
}

StrBlob的拷贝,赋值和销毁

略(详见书本)

12.1.2直接内存管理

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

int *pi = new int; //pi指向一个动态分配的、未初始化的无名对象

string *ps = new string; //初始化为空string
int *pi = new int; // pi指向一个未初始化的int

int *pi = new int(1024); //pi指向的对象的值为1024
string *ps = new string(10,'9');//ps为9999999999
//vector有10个元素,值依次从0到9
vector<int> *pv = new vector<int>{0,1,2,3,4,5,6,7,8,9};

string *ps1 = new string; //默认初始化为空string
string *ps = new string(); //值初始化啊为空string
int *pi1 = new int; //默认初始化:*pi1的值未定义
int *pi2 = new int(); //值初始化为0;*pi2为0

//若提供一个括号包围的初始化器,就可以使用auto。从此初始化器来推断我们想要分配的对象的类型。
//但是,由于编译器要用初始化器的类型来推断要分配的类型,只有当括号中仅有单一初始化器时才可使用auto
auto p1 = new auto(obj); //p指向一个与obj类型相同的对象
                         //该对象用obj进行初始化
auto p2 = new auto{a,b,c}; //错误:括号中只能有蛋哥初始化器

动态分配的const对象

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

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


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

释放动态内存


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

指针值和delete

int i,*pi1 = &i,*pi2 = nullptr;
double *pd = new double(33),*pd2 = pd;
delete i;    //错误,i不是一个指针
delete pi1;  //未定义:pi1指向一个局部变量
delete pd;   //正确
delete pd2;  //未定义:pd2指向的内存已经被释放了
delete pi2;  //正确:释放一个空指针总是没有错误的

//虽然一个const对象的值不能被改变,但其本身是可以被销毁的。如同其他动态对象一样,想要释放一个const
//动态内存对象,只要delete指向它的指针即可
const itn *pci = new const int(1024);
delete pci; //正确:释放一个const对象

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

//factory返回一个指针,指向一个动态分配的对象
Foo* factory(T arg)
{
    //看情况处理arg
    return new Foo(arg); //调用者负责释放此内存
}

//下面函数调用factoty,后者分配一个类型为Foo的新对象。当use_factory返回时,局部变量p被销毁
//此变量是一个内置指针,而而不是一个智能指针
void use_factory(T arg)
{
    Foo *p = factory(arg);
    //使用p但不delete它
}//p离开了它的作用域,但它所指向的内存没有被释放


//下面函数中p时指向factory分配的内存的唯一指针,一旦use_factory返回,程序就没有办法释放这块内存了。
//根据整个程序的逻辑,修正这个错误的正确方式是在use_factory中记得释放内存
void use_factory(T arg)
{
    Foo *p = factory(arg);
    //使用p
    delete p;//现在记得释放内存,我们已经不需要它了
}

//还有有一种可能,我们的系统中的其他代码要使用use_factory所分配的对象,我们就应该修改此函数
//让其返回以一个指针,指向它分配的内存
Foo* use_factory(T arg)
{
    Foo *p = factory(arg);
    //使用p
    return p;//调用者必须释放内存
}

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

1.忘记delete内存

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

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

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

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

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

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

12.1.3 shared_ptr和new结合使用

//new返回的指针来初始化智能指针
shared_ptr<double>p1;//shared_ptr可以指向一个double
shared_ptr<int>p2(new int(42));//p2指向一个值为42的int

//接受指针参数的智能指针构造函数时explicit的,因此不能将一个内置指针隐式转化为一个
//智能指针,必须使用直接初始化形式来初始化一个智能指针
shared_ptr<int>p1 = new int(1024); //错误:必须使用直接初始化形式
shared_ptr<int>p2(new int(1024));  //正确:使用了直接初始化形式

//由于不能进行内置指针到智能指针之间的隐式转换,一个返回shared_ptr的函数不能在
//其返回语句中隐式转换一个普通指针
shared_ptr<int> clone(int p)
{
    return new int(p); //错误:隐式转换为shared_ptr<int>
}

//我们必须将shared_ptr显示绑定到一个想要返回的指针上
shared_ptr<int>clone(int p)
{
    //正确:显示地用int*创建shared_ptr<int>
    return shared_ptr<int>(new int(p));
}

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

//考虑下面对shared_ptr进行操作地函数
//在函数被调用时ptr被创建并初始化
void process(shared_ptr<int>ptr)
{
    //使用ptr
}//ptr离开作用域,被销毁

//此函数的正确方法是传递给它一个shared_ptr
shared_ptr<int>p(new int(42));   //引用计数为1
process(p);   //拷贝p会递增它的引用计数;在process中引用计数值为2
int i = *p;   //正确:引用计数为1

int *x(new int(1024));  //危险:x是一个普通指针,不是一个智能指针
process(x);  //错误:不能将int*转换为一个shared_ptr<int>
process(shared_ptr<int>(x));  //合法的,但内存会被释放
int j = *x; //未定义的:x是一个空悬指针

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

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

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

其他shared_ptr操作

//可以用reset来将一个新的指针赋予一个shared_ptr
p = new int(1024); //错误:不能将一个指针赋予shared_ptr
p.reset(new int(1024)); //正确:p指向一个新对象

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

12.1.4 智能指针和异常


//如果使用智能指针。即使程序过早结束,智能指针类也能确保在内存不再需要是将其释放
void f()
{
    shared_ptr<int> sp(new int(42)); // 分配一个新对象
    //这段代码抛出一个异常,且在f中为未被捕获
}//这段函数结束时shared_ptr自动释放内存

//如果使用内置指针管理内存,且在new之后对应的delete之前发生了异常,则内存不会被释放
void f()
{
    int *ip = new int(42); //动态分配一个新对象
    //这段代码抛出一个异常,且在f中未被捕获
    delete ip;     //在退出 之前释放内存
}

智能指针和哑类


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

使用我们自己的释放操作

//为了用shared_ptr来管理一个connection 我们必须首先定义一个函数来代替delete
//这个删除器函数必须能够完成对shared_ptr中保存的指针进行释放的操作
//在本例中,我们的删除器必须接受单个类型为connection*的参数
void end_connection(connection *p){ disconnect(*p);}

//当我们创建一个shared_ptr时,可以传递一个(可选的)指向删除器函数的参数
void f(destination &d/*其他参数*/)
{
    connection c = connect(&d);
    shared_ptr<connection>p(&c,end_connection);
    //使用链接
    //当f退出时(即使是由于异常而退出),connection会被正确关闭
}

12.1.5unique_ptr

12.2 动态数组

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

小结

  • 7
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值