C++11(11):动态内存

动态分配的内存,只有在显式释放是,这些对象才会销毁。但是标准库中的两智能指针可以确保自动释放
除了静态内存和栈内存,每个程序还拥有一个内存池。这部分内存被称作自由空间或堆。
静态内存用来保存局部static对象、类static数据成员以及定义在任何函数之外的变量。栈内存用来保存定义在函数没得非static对象。

智能指针:三个类都定义在memory头文件中:
shared_ptr允许多个指针指向同一个对象、
unique_ptr则“独占”所指向的对象
weak_ptr标准库定义的伴随类,他是一种弱引用,指向shared_ptr所管理的对象

shared_ptr和unique_ptr都支持的操作:
shared_ptr<T> sp     空智能指针,可以指向类型为T的对象
unique_ptr<T> up
p    如果在一个判断中使用智能指针,效果就是检测它是否为空。
*p      解引用
p->mem     等价于(*p).mem
p.get()      返回p中保存的指针。要小心使用,若智能指针释放了其对象,返回的指针所指向的对象也就消失了。
swap(p , q)   交换p 和 q中的指针
p.swap(q)

shared_ptr独有的操作:
make_shared<T>(args)      返回一个shared_ptr,指向一个动态分配的类型为T的对象。使用args初始化此对象
shared_ptr<T>p(q)     p是shared_ptr q的拷贝;此操作会递增q中的计数器。q中的指针必须能转换成T*
p = q   p和q都是shared_ptr,所保存的指针必须能相互转换。此操作会递减p原来指向对象的引用计数,递增q原来对象的引用计数;若p引用计数变为0,则将其管理的原内存释放。
p.unique()   若p.use_count()为1,饭后true,否则返回false
p.use_count()  返回与p共享对象的智能指针数量;可能很慢,主要用于调试

shared_ptr<int> p3 = make_shared<int>(42);     用其参数构造智能指针,可将前面的类型名改为auto更简单
如果你忘记了销毁程序中不在需要的shared_ptr,程序仍会正确执行。但会浪费内存。share_ptr在无用之后仍然保留的一种情况是,你将shared_ptr存放在一个容器中,随后重排了容器。从而不再需要某些元素。这种情况下,应该确保用erase删除那些不需要的shared_ptr元素

使用动态生存期的资源的类:程序不知道自己需要使用多少对象,程序不知道所需对象的准确类型,程序需要在多个对象间共享数据
容器类是出于第一中原因而使用动态内存的典型例子

如果两个对象共享底层的数据,当某个对象被销毁时,我们不能单方面地销毁底层数据。
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();
pravite:
    std::shared_ptr<std::vector<std::string>> data;
    void check(size_type i,const std::string &msg)const;
};

StrBlob::StrBlob():data(make_shared<vector<string>>()){}
StrBlob::StrBlob(initializer_list<string> il) : data(make_shared<vector<string>>(il){}
void StrBlob::check(size_type i, const string &msg) const
{
    if(i >= data->size() )
        throw out_of_range(msg);
}
string& StrBlob::front()
{
    check(0,"front on empty StrBlob");
    return data->front();
}
string& StrBlob::back()
{
    check(0,"back on empty StrBlob");
    return data->back();
}
string& StrBlob::pop_back()
{
    check(0,"pop_back on empty StrBlob");
    return data->pop_back();
}
front和back应该对const进行重载


也可以对动态分配的对象进行值初始化,只需在类型名之前后加空括号即可:
int *p1= new int;   ///默认初始化     *p1的值未定义
int *p2 = new int();   //值初始化为0,*p2为0
对于定义了再的构造函数的类类型来说,要求值初始化是么有意义的:不管采用什么形式,对象都会通过默认构造函数来初始化,但对于内置类型有很大的差别

auto p1 = new auto(obj); //p1指向一个与obj类型相同的对象,该对象用obj进行了初始化
auto p2 = new auto{a,b,c};  ///错误,括号中只能有单个初始化器

用new分配const对象时合法的:
const int *p = new const int (1024);

如果内存耗尽
int *p=new int;//若果分配内存失败,new会抛出std::bad_alloc
int *p=new (nothrow) int;    // 如果分配失败,new返回一个空指针
这种形式的new成为定位new,允许我们向new传递额外的参数。nothrow意思是不抛出异常,而是返回以个空指针
bad_alloc和nothrow都定义在头文件new中。

与new类似,delete表达式也执行两个动作:销毁给定的指针指向的对象,释放对应的内存。
我们传递给delete的指针必须是指向动态分配的内容,或者是一个空指针。释放一块非new分配的内存,或将相同的指针值释放多次,行为都是未定义的,nullptr也只能释放一次,delete一个const动态对象,只要delete指向它的指针即可;
 在delete一个指针后,指针值就是无效的了,就变成我们说说的空悬指针,可以再delete后赋值nullptr,但只对于对个指针指向同一个动态内存块是无效的。赋值nullptr只是保证了当前指针,但是别的指针无法抱枕。
我们可以用new来初始化智能指针,:
shared_ptr <int>p (new int(42));
shared_ptr <int>p =new int(42);//错误,接受指针参数的智能指针的构造函数是explicit的。
//同样的,函数返回值也必须如此
shared_ptr<int> clone(int p) { return new int(p);}  //错
shared_ptr<int> clone(int p) { return shared_ptr<int> (new int (p));}
默认情况,一个用来初始化智能指针的普通指针必须指向动态内存,因为智能指针默认使用delete释放所关联的对象。(可自己定义)

定义和改变shared_ptr的其他方法
shared_ptr<T> p(q)      p管理内置指针q所指向的对象;q必须指向new分配的内存,且能够转换成T*类型;
shared_ptr<T> p(u)      p从unique_ptr u那里接管了对象的所有权;将u置为空
shared_ptr<T> P(q,d)     p接管了内置指针q所指向的对象的所有权。q必须能转换成T*类型,p将使用的调用对象d来代替delete
shared_ptr<T> p(p2,d)     p是shared_ptr p2的拷贝,p将用可调用对象来代替delete
p.reset()     p.reset(q)    p.reset(q,d)
若p是唯一的指向其对象的shared_ptr,reset会释放此对象。若传递了可选参数内置指针q,会令p指向q,否则会将p置为空。d来替换delete
 
当将一个shared_ptr绑定到一个普通指针时,我们就将内存管理的责任交给了这个shared_ptr。一旦这样做了,我们就不应该在使用内置指针来访问shared_ptr所指向的内存了。即,不要混合使用普通指针和智能指针。
智能指针定义了一个get成员函数,返回一个指向智能指针管理对象的内置指针。是为了那些不能传递智能指针的代码而设计的。
get用来将指针的访问权限传递给代码,你只有在确定代码不会delete指针的情况下,才能使用get。特别是,永远不要用get初始化另一个智能指针或者为另一个智能指针赋值

p=new int(1024)  //错误,不能将一个指针赋予shared_ptr
p.reset( new int( 1024));  //正确
与赋值雷系,reset会更新引用计数,如果需要会释放p指向的对象。常用unique一起使用
if(!p.unique())
    p.reset(new string(*p));//我们不是唯一用户;分配新的拷贝
*p +=newVal; // 现在我们知道自己是唯一的用户,可以改变对象的值。

函数退出有两种可能,正常处理和发生了异常,无论哪种,局部对象都会被释放。也就是使用智能指针在发生异常是也是安全的,但是使用delete时,如果在没有delete前抛出异常,那么内存将不会被释放。
如果在new和delete之间发生了异常,而且异常未在f中被捕获,则内存就永远不会被释放了。
对于那些没有析构函数的类。使用智能指针是一个有效防止内存泄露的方法。
我们可以定义自己的释放器,
void end_connection(connection *p){ disconnect( *p);}
void f(destination &d)
{
    connet c = connect(&d);
    shared_ptr<connection> p (&c,end_connection);
}
当p被销毁时,调用的是end_connection。

智能指针的陷阱:
不使用相同的内置指针初始化(或reset)多个智能指针
不delete  get() 返回的指针
不使get()初始化或reset另一个智能指针。
如果你使用get()返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就变为无效的了。
如果你使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器。

unique_ptr的操作:
unique_ptr<T> u1          unique_ptr<T,D>  u2
空unique_ptr,可以指向类型为T的对象。u1会使用delete来释放它的指针。u2会使用一个类型为D的可调用对象来释放它的指针。
unique_ptr<T,D> u(d)    空unique_ptr,指向类型为T的对象,用类型为D的对象d代替delete
u = nullptr    释放u指向的对象,将u置为空
u.release()     u放弃对指针的控制权,返回指针,并将u置空
u.reset()     释放u指向的对象。
u.reset(q)     如果提供了内置指针q,令u指向这个对象;否则将u置空
u.reset(nullptr)    
虽然不能拷贝赋值unique_ptr,但是可以通过release和reset将指针的所有权进行转移。
unique_ptr<string> p2(p1.release());
unique_ptr<string> p3(new string("Trex"));
p2.reset(p3.release()); //p3置为空,将对象的所有权转移给了p2
对于release 会切断unique_ptr和原来管理对象间的联系。返回一个指针通常用来初始化另一个指针,达到管理权转移的目的。
如果我们不用另一个智能指针来保存release返回的指针,我们的程序就要负责释放资源了:
auto p = p2.release();
delete(p);

不能拷贝unique_ptr的规则有个例外:我们可以拷贝或赋值一个将要被销毁的unique_ptr。最常见的就是从函数返回一个unique_ptr:
unique_ptr<int> clone(int p){ return unique_ptr<int>(new int(p));}
也可以返回局部变量:
unique_ptr<int> clone ( int p)
{
    unique_ptr<int> ret(new int (P));
    return ret;
}
编译器知道要返回的对象将要被销毁。因此,编译器执行一种特殊的“拷贝”、

(早起的c++包含了auto_ptr类,具有unique_ptr的部分特性,但不是全部。我们不能再容器中保存auto_ptr,也不能从函数中返回,虽然它仍是标准的一部分,但编写程序时还是用unique_ptr比较好)

重写链接程序:
void f(destination &d)
{
    connection c = connect(&d);
    unique_ptr<connection,decltype(end_connection)*> p(&c , end_connection);
}

weak_ptr是一种不控制所指对象生存期的智能指针,它指向有一个shared_ptr绑定的对象。将weak_ptr绑定到一个shared_ptr不会改变其引用计数。一旦对象的shared_ptr被销毁,即使有weak_ptr指向对象,对象也会被销毁。

weak_ptr<T> w    空weak_ptr可以指向类型为T的对象
weak_ptr<T> w(sp)      与shared_ptr sp指向相同对象的weak_ptr、T必须能转换为sp指向的类型。
w = p      p可以是一个shared_ptr或一个weak_ptr。赋值后w与p共享对象
w.reset()    将w置为空
w.use_count()       与w共享对象的shared_ptr的数量
w.expired()     若w.used_count()为0,返回true,否则返回false
w.lock()       如果expired为true,返回一个空的shared_ptr,否则返回一个指向w的对象的shared_ptr

 auto p = make_shared_ptr<int>(42);
weak_ptr<int> wp(p)
由于对象可能不存在,我们不能使用weak_ptr直接访问对象,而是必须调用lock,检查weak_ptr指向的对象是否任然存在。
if(shared_ptr<int> np = wp.lock()) { /*....*/ }

为StrBlob类定义一个伴随指针类。有两个数据成员,wptr,或者为空,或者指向一个StrBlob中的vector;curr,保存当前对象所表示的元素的下标。
class StrBlobPtr{
public:
    StrBlobPtr():curr(0){ }
    StrBlobPtr(StrBlob &a,size_t sz=0) : wptr(a.data) , curr(sz) { }
    std::string& deref() const;
    StrBlobPtr& incr()        //前缀递增
private:
    std::shared_ptr<std::vector<std::string&>> check(std::size_t , const std::string&) const;
    std::weak_ptr<std::vector<std::string>> wptr;
    std::size_t curr;
};
//我们不能将一个StrBlobPtr绑定到一个const StrBlob对象上,构造函数么有重载const版本
std::shared_ptr<std::vector<std::string>> StrBlobPtr::check(std::size_t i , const std::string &msg) const
{
    auto ret = wptr.lock();
    if(!ret)
        throw std::runtime_error("unbound StrBlobPtr");
    if( i >= ret->size())
        throw std::out_of_range(msg);
    return ret;
}
std::string & StrBlobPtr::deref() const
{
    auto p = check(curr, "dereference past end");
    return (*p)[curr];
}
StrBlobPtr& StrBlobPtr::incr()
{
    check(curr,"increment past end of StrBlobPtr");
    ++curr;
    return *this;
}
为了访问data成员,我们的指针类必须声明为StrBlob的friend                        friend class StrBlobPtr;

大多数应用应该使用标准库容器而不是动态分配的数组。使用容器更简洁,不容易出现内存管理的作物病且可能有更好的性能。
int *pia = new int[get_size()];
typedef int arrT[42];
int *p = new arrT;
因为分配的内存并不是一个数组类型,而是一个元素类型的指针。因此不能对动态数组调用begin和end,同样也不能使用foreach语句
初始化:
int *pia = new int[10];//10个未初始化的int
int *pia2 = new int[10]();  // 10个值初始化为0的int
string *psa = new string[10];   //10个空string
string *psa2 = new sting[10]();   ///10个空string
int *pia3 = new int[10]{0,1,2,3,4,5,6,7,8,9}//列表初始化
对于列表初始化,如果初始化数目小于元素数目,剩余元素将进行值初始化。如果多余,则new表达式失败,不会分配任何内存。会抛出以个类型为bad_array_new_length的异常。类似bad_alloc,定义在new头文件中
对于new对象。我们可是使用传统的构造方式(使用圆括号),也可以使用初始化列表。
虽然我们用空括号对数组进行初始化,但不能在括号中给出初始化器,这意味着不能用auto分配数组

size_t n =get_size();
int* p= new int[n];
如果get_size返回的是0,那么代码仍然可以正确工作。对于零长度的数组来说,返回的指针就像尾后指针一样,我们可以像尾后迭代器一样使用这个指针。但不可以解引用
释放  delete [ ] pa;

使用unique_ptr来管理new的数组
unique_ptr<int[ ]> up(new int[10]);
up.release();//自动用delete[]销毁其指针。
当一个unique_ptr指向一个数组时,我们不能使用点和箭头成员运算符。但是可以使用下标来访问元素

unique_ptr<T[ ]> u      u可以指向一个动态分配的数组,数组元素类型为T
unique_ptr<T[ ]> u(p)    u指向内置指针p所指向的动态分配的数组。p必须能转换成类型T*
u[i]      返回u拥有的数组中位置i处的对象,u必须指向一个数组。

shared_ptr  不直接支持管理动态数组。如果希望使用shared_ptr管理一个动态数组,必须提供自己定义的删除器,因为默认情况下是使用delete来删除对象的。
shared_ptr <int> sp(new int[10] , [ ](int *p){delete []p;});
sp.reset();
shared_ptr不直接支持动态数组管理这一特性会影响我们如何访问数组中的元素,它未定义下标运算符
for(size_t i = 0; i != 10; ++i)
    *(sp.get() + i) = i;
//shared_ptr未定义下标运算,而且智能指针类型不支持指针算术运算。

对于使用new,我们要在开始就赋值,这可能带来不必要的浪费。有些对象不会被用到,但开始时我们并不知道。如果是string,每个被使用的元素都被赋值两次:默认初始化,随后的赋值。更重要的是,那些没有默认构造函数的类就不能动态分配数组了。
allocator类定义在memory中,帮助我们将内存分配和对象的构造分离开来,它分配的内存是原始的,未构造的。
allocator<string> alloc;
auto const p = alloc.allocate(n);//分配n个未初始化的string

标准库allocator类及其算法:
allocator<T> a   定义一个对象,可以为类型为T的对象分配内存
a.allocate(n)      分配一段原始的、未构造的内存。保存n个类型为T的对象。
a.deallocate(p,n)     释放从T*指针p中地址开始的内存,这块内存保存了n个类型为T的对象;p必须是一个先由allocate返回的指针,且n必须是p创建时所要求的大小。在调用deallocate之前,用户需对每个在这块内存中创建的对象调用destroy
a.construct(p,args)      p必须是一个类型为T*的指针,指向一块原始内存;args被传递给类型为T的构造函数,用来在p指向的内存中构造一个对象。
a.destroy(p)    p为T*类型的指针,次算法对p指向的对象执行析构函数

auto  q=p;//     q指向最后构造的元素之后的位置
alloc.construct(q++);   //*q为空字符串
alloc.construct(q++, 10, 'c');
alloc.construct(q++, "hi");
while(q != p)
        alloc.destroy(--q);
alloc.deallocte(p,n);   //归还内存,

allocator的伴随算法,可以再未初始化的内存中创建对象。
这些函数在给定目的位置创建元素,而不是有系统分配内存给他们。
uninitialized_copy(b,e,b2)    从迭代器b和e指出的输入范围中拷贝元素到迭代器b2指定的未构造的原始内存中。b2指向的内存必须足够大,能容纳输入序列中元素的拷贝
uninitialized_copy_n(b,n,b2)   从迭代器b指向的元素开始,拷贝n个元素到b2开始的内存中
uninitialized_fill(b,e,t)    在迭代器b和e指定的原始内存范围中创建对象,对象的值均为t的拷贝
uninitialized_fill_n(b,n,t)   从迭代器b指向的内存地址开始创建n个对象。b必须指定最够大的未构造的原始内存,能够容纳给定数量的对象

auto p=alloc.allocate(vi.size() * 2);
auto q=uninitialized_copy(vi.begin(),vi.end(),p); // 返回一个指针,指向最后一个构造的元素之后的位置。
uninitialized_fill_n(q,vi.size(),42);  //将剩余元素初始化为42;


使用标准库:文本查询程序:实现数据在类之间的共享
class QueryResult;
class TextQuery{
public:
    using line_no = std::vector<std::string>::size_type;
    TextQuery(std::ifstream& );
    QueryResult query(const std::string& ) const;
private:
    std::shared_ptr<std::vector<std::string>> file;
    std::map<std::string , std::shared_ptr<std::set<line_no>>> wm;
};
TextQuery::TextQuery(ifstream &is) : file(new vector<string>)
{
    string text;
    while(getline(is,text))
    {
        file->push_back(text);
        int n = file->size() - 1;
        istringstrem line(text);
        string word;
        while(line>>word)
        {
             auto &lines = wm[word];
            if ( ! lines)
                lines.reset(new set<line_no>);
            lines->insert(n);   
        }
    }
}
class QueryResult{
friend std::ostream& print(std::ostream&, const QueryResult& );
public:
    QueryResult(std::string s, std::shared_ptr<std::set<line_no>> p, std::shared_ptr<std::vector<std::string>> f)
                :  sought(s) , lines(p) , file(f) {  }
private:
    std::string sought;
    std::shared_ptr<std::set<line_on>> lines;
    std::shared_ptr<std::vector<std::string>> file;
}
QueryResult TextQuery::query(const string &sought)const
{
    static shared_ptr<set<line_no>> nodata(new set<line_no>);
    auto loc = wm.find(sought);
    if(loc == wm.end())
        return QueryResult(sought, nodata, file);
    else
        return QueryResult(sought, loc->second, file);
}
ostream &print(ostrem & os, const QueryResult &qr)
{
    os<<qr.sought<<" occurs "<<qr.liens->size()<< " "<<make_plural(qr.lines->size(), "times", "s")<<endl;
    for(auto num: *qr.lines)
        os<<"\t(line "<<num + 1 <<") "<< * (qr.file->begin() + num)<<endl;
    return os;
}
void runQueries(ifstream &infile)
{
    TextQuery tq(infile);
    while(true)
    {
        cout << "enter word to look for, or q to quit: ";
        string s;
        if(!(cin >> s) || s == "q") break;
        print (cout, tq.query(s))<<endl;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值