c++ 动态内存

c++ 第十二章:动态内存

1:对于一个c++程序,我们使用到的内存有栈内存静态内存以及动态内存,静态内存用来保存局部static,全局变量等对象,栈内存用来保存在函数中申请的非static对象,它们都是由编译器创建和销毁的。此外,我们在程序运行过程中,如果需要分配的动态内存,即c语言中的malloc,是从堆上分配的。但是释放也必须要我们手动释放,由于正确管理动态内存非常的棘手,c++为我们提供了管理动态内存的两个指针,shared_ptr和unique_ptr来让我们从正确释放动态内存这一件麻烦事中解放出来。

2:shared_ptr类

(1):基本操作

操作含义
shared_ptr sp空智能指针,指向一个T类型
if(sp)将sp作为条件,如果指向一个对象返回true
*P解引用操作,得到指针指向的对象
p->mem等价于(*p).mem
p.get()返回p中保存的指针,返回的是一个内置指针,不是智能指针
swap(p,q)交换p和q中的指针
make_shared(args)返回一个shared_ptr,指向一个动态分配的类型为T的对象,auto sp = make_shared() sp是一个shared_ptr 类型
shared_ptr p(q)p是shared_ptr q 的拷贝,此操作会递增q中的计数器,q中的指针必须能转换成*T
p = qp和q都是shared_ptr,此操作会递减p的引用计数,递增q的引用计数,若p的引用计数变为0,则将其管理的原内存释放
p.use_count()返回与p共享对象的智能指针的数量
p.unique()如果p.use_count()为1,返回true,否则返回false

(2):拷贝和赋值

每个shared_ptr都有一个关联的计数器,通常称之为引用计数,即表示的是现在的shared_ptr与多少个对象之间有联系,当引用计数变为0的时候,此shared_ptr指向的对象就会被释放。
增加引用计数的操作:拷贝shared_ptr,用a初始化b,a的引用计数增加。将shared_ptr作为参数传递或者作为函数的返回值。
减少引用计数的操作:给shared_ptr赋予新值或者shared_ptr被销毁。

#include<iostream>
#include<memory>

using std::cout;
using std::cin;
using std::endl;

int main(int argc,char *argv[])
{
    auto sp = std::make_shared<int>(42);
    auto q = std::make_shared<int>(50);
    cout << *sp << endl;
    //递增q指向的对象的引用计数,递减sp原来指向的对象的引用计数,此时sp引用计数为0,会被释放
    sp = q;

    return 0;
}

(3):shared_ptr自动销毁所管理的对象释放内存

shared_ptr自动释放相关联的内存是用析构函数完成销毁工作的。对于shared_ptr我们保证只要有任何shared_ptr对象引用它,它就不会被释放掉。因此处理无用的shared_ptr就变得至关重要,如果我们将shared_ptr存储在一个容器中,以后不再需要全部元素,而只使用其中的一部分,要记得用erase删除掉不需要的元素。

(4):使用动态内存的一个好处是允许多个对象共享相同的底层数据。也就是底层的数据只有一份,各个类之间共享。当引用计数变为0的时候,会被销毁。
看一个例子:

#include<iostream>
#include<memory>
#include<string>
#include<vector>

using std::cout;
using std::cin;
using std::endl;

class StrBlob {:
public:
    typedef std::vector<std::string>::size_type 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;        //构造的是shared_ptr的data
    void check(size_type i,const std::string &msg) const;
};
//StrBlob的两个构造函数
StrBlob::StrBlob() : data(std::make_shared<std::vector<std::string>>()) {}   //通过make_shared来使用
StrBlob::StrBlob(std::initializer_list<std::string> il) : data(std::make_shared<std::vector<std::string>>(il)) {}

void StrBlob::check(size_type i,const std::string &msg) const
{
    if(i > data->size()) {
        throw std::out_of_range(msg);
    }
}
//相当于自己重写front和back
std::string & StrBlob::front()
{
    check(0,"front on empty StrBlob");
    return data->front();
}
std::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();
}

int main(int argc,char *argv[])
{
    StrBlob blob{"nihao","wo","jiao"};
    blob.push_back("yangbodong");
    cout << blob.back() << endl;   //输出yangbodong
    blob.pop_back();
    cout << blob.back() << endl;   //输出jiao
    return 0;
}

3:new和delete

new:相当于c语言中的malloc,动态申请内存。

(1):申请空间

int *p1 = new int;    //从动态内存中分配出来一块大小为int的空间,未命名。
int *p2 = new int(5); //p2指向的对象值为5
std::string *str = new std::string; //str指向的字符串为空。
std::string *str1 = new std::string("yangbodong"); //str1指向的内容为yangbodong
auto p1 = new auto(obj);  //根据obj的类型推断出p1的类型
const int *pci = new const int(1024); //new返回的是一个指向const的指针。

(2):内存耗尽

正常情况下内存耗尽,new会失败,这时候它会抛出一个异常,类型为bad_alloc,但是我们可以用nothrow来阻止它抛出异常。我们称这种形式的new 为定位new。

int *p2 = new(nothrow) int;   //如果分配失败,则new返回一个空指针。

delete:相当于c语言中的free,释放动态释放的内存。

int i,*pi1 = &i,*pi2 = nullptr;
double *pd = new double(33.33), *pd2 = pd;

delete(i);          //delete传递的参数是指针
delete(pi1);        //释放局部变量是未定义的
delete(pd);         //正确,删除pd指向的空间
delete(pd2);        //已经被删除,结果是未定义的
delete(pi2);        //正确,删除一个没有指向的指针总是正确的

4:定义和改变shared_ptr的方法。

操作含义
shared_ptr p(u)p管理内置指针q所指向的对象,q必须指向new分配的内存,且能转换成T*类型
shared_ptr p(u)p从unique_ptr u那里接管了对象的所有权,将u置为空
shared_ptr p(q,d)p从q接管了q所指向的对象的所有权,q必须能转换成T*类型,p调用d来代替delete
shared_ptr p(p2,d)p是shared_ptr p2的拷贝,p调用d来代替delete
p.reset(),p.reset(q),p.reset(q,d)若p是唯一指向其对象的shared_ptr,reset会释放此对象。若传递了可选的参数内置指针q,会另p指向q,否则会将p置为空,若还传递了参数d,将会调用d而不是delete来释放q

注意:

  • 不要将智能指针和普通指针混合使用。
  • 不要用get方法初始化另一个智能指针或者为另一个智能指针赋值。

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

//通常我们会与unique来一起使用,先检查它原来是不是唯一用户,要是不是,我们调用reset将内容拷贝一份
if(!p.unique()) {
    p.reset(new std::string(*p));
}
*p += newVal;

5:unique_ptr

操作含义
unique_ptr u1,unique_ptr u2空unique_ptr,可以指向类型为T的对象。u1会使用delete来释放它的指针,u2使用类型为D的可调用对象释放它的指针
u=nullptr释放u指向的对象,将u置为空
u.release()u放弃对指针的控制权,返回指针,并且将u置为空
u.reset() u.reset(q) u.reset(nullptr)释放u指向的对象,如果提供了q,则将u指向q,否则置为空

注意:

  • unique_ptr不支持拷贝和赋值操作。但是例外是可以拷贝或赋值一个将要被销毁的unique_ptr。比如从一个函数return一个unique_ptr。
  • 可以向unique_ptr传递一个删除器。

6:weak_ptr

它是一种不控制所指向对象生存期的智能指针,它指向由一个shared_ptr管理的对象。但是将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。并且一旦最后一个指向对象的shared_ptr被释放,即使有weak_ptr指向对象。对象还是会被释放。

操作含义
weak_ptr w空的weak_ptr可以指向类型为T的对象
weak_ptr w(sp)与shared_ptr指向相同对象的weak_ptr。
w = pp是一个shared_ptr或者weak_ptr,赋值后共享
w.reset()将w置为空
w.use_count()与w共享对象的shared_ptr的数量
w.expired()如果w.use_count()为0,返回true
w.lock()如果expired为true,返回一个空的shared_ptr,否则返回一个指向w的对象的shared_ptr
#include<iostream>
#include<memory>

using std::cout;
using std::cin;
using std::endl;

int main(int argc,char *argv[])
{
    auto p = std::make_shared<int>(42);
    cout << p.use_count() << endl;  // 1
    std::weak_ptr<int> wp(p);
    cout << p.use_count() << endl;  // 1
    std::shared_ptr<int> wp1(p);
    cout << p.use_count() << endl;  // 2

    //由于对象可能不存在,因此我们不能使用weak_ptr直接访问对象,而必须调用lock,此函数检查weak_ptr指向的对象是否存在,如果存在,lock返回一个指向共享对象的shared_ptr。
    if(std::shared_ptr<int> np = wp.lock()) {
    //此时,np和wp共享同一个shared_ptr
}

    return 0;
}

7:new和数组

我们可以通过调用new申请一个数组的大小。new返回的是一个指向第一个元素的指针。

//初始化
int *pia = new int[10];      //10个未初始化的int
int *pia2 = new int[10]();   //10个为0的int
std::string *psa = new std::string[10];  //10个空string
std::string *psa2 = new std::string[10](); //10个空string
std::string *psa3 = new std::string[10]("a","aa","aaa");  //前三个用初始化器初始化,后面的用值初始化。

注意:我们可以int *p = new int[0],返回的是合法的非空指针,对于零长度来说,此指针就像尾后指针,但是不可以用它来解引用,毕竟它不指向任何元素。

//释放动态数组  
delete p;  //p必须指向一个动态分配的对象或者为空
delete[] p;//p必须指向一个动态分配的数组或者为空

注意!!!!
要是对象,就用p,要是数组,一定要用[]p。

智能指针和动态数组

标准库中提供了一个可以管理new分配的数组的unique_ptr版本。为了用unique_ptr,我们必须在对象类型后面跟一对花括号。

std::unique_ptr<int[]> up(new int[10]); //unique_ptr指向的是一个拥有十个int元素的数组。
up.release(); //自动删除delete[]

注意:

  • 当一个unique_ptr指向一个数组的时候,我们不能使用点和箭头来访问成员。
  • shared_ptr不支持直接管理动态数组,要是非要使用shared_ptr管理,我们必须提供删除器。由于shared_ptr未定义下标运算符,因此我们需要用get获取一个内置指针,然后用它访问数组元素。

8:allocator类

1:

操作含义
allocator a定义了一个名为a的allocator对象,它可以为类型为T的对象分配内存
a.allocate(n)分配一段原始的、未构造的内存,保存n个类型为T的对象
a.deallocate(p,n)释放从p中地址开始的内存,这块内存保存了n个类型为T的对象,p是一个由allocate返回的指针,且n必须是p创建时的大小.
a.construct(p,args)p必须是T*,指向一块原始内存,arg被传递给类型为T的构造函数,用来在p指向的内存中构造一个对象
a.destroy(p)对p指向的对象调用析构函数

注意:

  • 为了使用allocate返回的内存,我们必须用construct来构造对象,不能使用未构造的内存。
  • 当我们用完对象,必须对每个构造的元素调用destroy来销毁它们。并且我们只能对真正构造了的元素进行destroy操作。
  • 将我们使用完的内存归还给系统,释放内存通过deallocate完成。

2:拷贝和填充未初始化内存的算法

操作含义
uninitialized_copy(b,e,b2)从迭代器b和e指出的输入范围中拷贝元素到迭代器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个对象
#include<iostream>
#include<memory>
#include<vector>

using std::cout;
using std::cin;
using std::endl;

int main(int argc,char *argv[])
{
    std::allocator<int> alloc;  //定义alloc
    std::vector<int> vec{1,2,3,4,5};
    //给p分配vec大小的2倍
    auto p = alloc.allocate(vec.size()*2);
    //将vec拷贝到q的前一半
    auto q = std::uninitialized_copy(vec.begin(),vec.end(),p);
    //后面的一半用42填
    std::uninitialized_fill_n(q,vec.size(),42);
    return 0;
}

posted on 2016-03-01 21:50  杨博东的博客  阅读(31)  评论(0编辑  收藏  举报

导航