• C++Primer学习——动态内存


    静态内存:用来保存static 栈内存:保存非static

    智能指针:

    shared_ptr:允许多个指针指向一个对象
    unique_ptr:独占所指对象
    weak_ptr:一种弱引用,指向shared_ptr的对象

    shared_ptr:

    shared_ptr<T> p;
    p.get();     //返回p中保存的指针
    p.unique();    //指针数量为1返回 
    p.use_count(); //返回共享指针数量
    shared_ptr<string > p = make_shared_ptr<string>(10,'9');
    shared_ptr<int> p = make_share_ptr<int>();
    auto p = make_share_ptr<int>();
    

    拷贝会使shared_ptr的计数器增加.初始化,函数参数或者函数返回值。
    一旦计数器为0时就会通过析构函数释放自己

    函数结束会返回一个p的拷贝,增加计数器,所以函数结束含有指针指向p的内存,所以不会被销毁

    void use_factory(T arg)
    {
       shared_ptr<Foo> p = factory(arg);
       return p;
    }
    

    所以如果你将shared_ptr放在一个容器中,而后不再需要全部的元素,记得把不用元素删除。

    使用动态生存周期的资源:
    1.程序不知道自己有多少的对象 //容器类
    2.程序不知道自己做需要对象的准确类型
    3.程序需要在多个对象间共享数据 //通过shared_ptr的计数器特性实现

    直接管理内存:

    通过new和delete来管理内存,与智能指针不同的是,直接管理内存无法依赖类对象拷贝、赋值和销毁操作的任何默认定义。

    默认初始化和值初始化:

    对于定义了构造函数的类类型来说两者都会调用构造函数初始化,没意义;但对于内置类型,值初始化有良好定义的值,而默认初始化
    的值是未定义的。

    int *p = new int;       //默认初始化,值未定义
    int *p = new int();     //值初始化为0,*p为0
    

    所以在定义const对象时,如果有构造函数的类类型其const对象可以隐式初始化,但是其他类型就必须显示初始化了。

    const int *pci = new const int(1024);
    const string *s = new const string;
    

    如果提供一个初始化器,就能使用auto判断我们想要的类型,但是只有当单一的初始化器时才能使用

    auto p = new auto(obj);
    auto p = new auto(a,b,c);  //error
    

    释放一块并非new分配的内存或者多次释放相同指针是未定义的行为。
    动态对象的生命周期到被释放为止:
    当离开作用域时,局部的指针被销毁了,但是所指内存并没有释放

    void use_factory(T arg)
    {
       Foo *p = factory(arg);
       //使用p但不delete
       //即便离开作用于,所指内存没被释放
    }
    

    常见问题:忘记delete、使用已经释放了的内存,同一块内存释放了两次

    shared_ptr和new的结合使用:

    接受指针参数的智能指针的构造函数是explicit,new会返回一个指向该对象的指针

    shared_ptr<int>p1 = new int(1024);       //error 需要隐式转换成shared_ptr
    shared_ptr<int>p2(new int(1024));
    shared_ptr<int> clone(int p)
    { 
       return new int(p);              //error  需要隐式转换成shared_ptr
       return shared_ptr<int>(new int(p));
    }
    

    默认情况下初始化智能指针的普通指针必需指向动态内存,因为智能指针默认使用delete释放。
    否则需要自定义操作来替代delete

    shared_ptr<T> p(q); //p管理q所指向的对象
    shared_ptr<T> p(u); //p接管unique_ptr并将u置为空
    shared_ptr<T> p(q,d); //p接管q,用d代替delete
    shared_ptr<T> p(p2,d); //p2的一个拷贝
    p.reset();
    p.reset(q);
    p.reset(q,d);
    

    不要混用普通指针和智能指针:

    当智能指针和普通指针指向了同一个内存,如果智能内存的计数减为0,那么内存会被释放
    所以当同时用的时候,将内存管理交给shared_ptr

    int *x = &q;
    process(shared_ptr<int>(x) );  //只能指针会被销毁,内存释放
    int j = *x;   //空悬指针
    

    可以通过get()向不能使用智能指针的传递一个内置指针,但get返回的指针不能delete
    get最好用来传递访问权限,不能用get初始化另外一个智能指针或者为另一个智能指针赋值

    无法将普通指针赋值给shared_ptr,所以需要reset
    p = new int(42); //error 不能隐式转换?
    p.reset(new int(42)); //correct

    对于异常,智能指针无论怎样都能确保内存释放,但是直接管理内存时,如果在delete之前异常弹出,那么直接管理的内存
    并不会释放。

    如果类没有析构函数而且没有显示的关闭连接,那么会出现资源泄露。
    可以考虑将其与一个shared_ptr绑定,然后指定delete该指针的方法,从而保证正常关闭

    void end_connection(connection *p) {disconnect(p);}
    void f(destination &d)
    {
       connection c = connect(&d);
       shared_ptr<connection> p(&c,end_connection);
    }
    

    智能指针error:

    1.不使用相同的内置指针(或者reset)初始化多个智能指针。 //计数器全是1?
    shared_ptrp1(p);
    shared_ptrp2(p);
    cout << p1.use_count() << " " <<p2.use_count() <<endl;
    //1 1
    2.不delete get()返回的指针
    3.不适用get初始化或者reset另一个智能指针
    4.注意get()返回的指针会在智能指针销毁后失效
    5.如果你管理的不是new分配的内存,指定一个删除器

    unique_ptr:

    没有类似make_shared的函数,只能绑定到一个new返回的指针上面。同样只能直接初始化

    unique_ptr<string> p1(new string("123"));
    unique_ptr<string> p2(p1.release());              
    //转移控制权,release放弃控制并返回指针
    unique_ptr<string> p3(new string("abc"));
    p2.reset(p3.release());
    而且可以拷贝一个将要被销毁的unique_ptr,因为编译器知道它将被销毁所以执行一种特殊的拷贝
    unique_ptr<int> clone(int p)
    {
        return unique_ptr<int>(new int(p));
    }
    

    unique_ptr可以自定义删除器:

    connection c = connect(&d);
    unique_ptr<connection , decltype(end_connection)*>
            p(&c,end_connection);
    

    weak_ptr:

    是一种不控制所指对象生存期的只能指针,指向一个shared_ptr对象,但不会改变引用计数。

    w.use_count();  //返回shared_ptr数量
    w.expired();    //use_count为0返回true
    w.lock();       //如果expired()为true返回一个空指针,否则返回一个shared_ptr
    

    动态数组:

    vector和string都是连续内存中保存元素,所以每次要分配大量内存。
    可以进行默认初始化或者值初始化,由于不能在()中给出初始化器,意味着不能auto分配数组

    int *p = new int[10];         //10个未初始化int
    int *p = new int[10]();       //10初始化为0的int
    int *p = new int[10]{0,1,2,3,4};    //前几个用给定初始化器初始化
    delete []p;      
    delete时[]告诉编译器指针指向对象数组的第一个元素,如果不加[]是未定义的行为。而且释放内存时逆序销毁
    

    1.与unique_ptr:

    unique_ptr<int[]> p(new int[10]);
    p.release();   //自动调用delete
    

    2.与shared_ptr:
    和unique不同的是,shared不支持自动管理动态数组QAQ,所以需要定义删除器
    如果没有删除器,那么产生问题和delete没加[]一样(即默认调用delete p)

    shared_ptr<int> p(new int[10],[](int *p){delete[] p;});
    p.reset();  //通过lambda表达式释放
    

    shared_ptr没有下标运算符,而且只能指针不支持指针算术运算,所以访问数组中的元素。
    必需要get()获取内置指针

    *(p.get() + i) = i;
    

    allocator

    在用new分配的时候,不知道数量所以可能把数组分配过大。 而且每个使用的元素都要赋值两次,默认初始化 + 赋值。
    如果没有默认构造函数,那么不能动态分配数组。

    allocator<string> alloc;
    auto p = alloc.allocate(5);        //分配5个未构造原始内存
    auto q = p;
    alloc.construct(q++,"Orz");
    cout << *p <<endl;
    alloc.destroy(--q);
    alloc.deallocate(p,5);
    destory() 释放对象内的动态内存(如果有) deallocate是释放对象本身占有的内存
    

    所以要先destory再进行deallocate

    unintialized_copy(b,e,b2):从迭代器b和e指出的输入范围中拷贝元素到迭代器b2指定的未构造的原始内存中。b2指向的内存必须足够大,能容纳输入序列中元素的拷贝
    uninitialized_copy_n(b,n,b2):从迭代器b指向的元素开始,拷贝n个元素到b2开始的内存
    unintialized_fill(b,e,b2):在[b,e]创建b2的拷贝
    uninitialized_fill_n(b,n,b2):在b开始创建b个
    
    pair<string*,string*>
    alloc_n_copy(const string* b,const string* e)
    {
       auto data = alloc.allocate(e-b);
       return {data,uninitialized_copy(b,e,data)};
    }
    
  • 相关阅读:
    Vue 配置多页面,去掉.html后缀的技巧
    uniapp img header 请求图片
    vscode 配置搜索过滤目录
    elementui eltable 开发环境 无法渲染,无法显示的问题
    isVnode 判断一个对象是否为 vnode 类型
    Cache缓存帮助类
    网络(NET)帮助类
    解读event.returnValue和return false
    sql server 生成连续日期和数字
    C#中对象与JSON字符串互相转换的三种方式
  • 原文地址:https://www.cnblogs.com/Przz/p/6416042.html
Copyright © 2020-2023  润新知