• C++_03_动态内存与智能指针


    动态内存

    • 静态内存:用来保存局部static对象、类static数据成员以及定义在任何函数之外的变量。
    • 栈内存:用来保存定义在函数内的非static对象。
    • 自由空间(free store)或堆(heap):程序用堆存储动态分配的对象——程序运行时分配的对象。

    分配在静态内存和栈内存中的对象由编译器自动创建和销毁。动态对象的生存期由程序来控制(代码显示的创建和销毁对象)。

    使用动态内存的原因:

    • 程序不知道自己需要使用多少对象;
    • 程序不知道所需对象的准确类型;
    • 程序需要在多个对象间共享数据;

    动态内存的使用很容易死出现问题:

    • 动态内存使用时,忘记释放内存,就会产生内存泄漏。
    • 有指针引用内存的情况下,我们就释放了它,导致引用非法内存的指针。

    智能指针

    C++11新标准库提供了两种智能指针类型来管理动态对象。智能指针的行为类似常规指针,重要区别是它负责自动释放所指向的对象。

    • stared_ptr: 允许多个指针指向同一个对象。
    • unique_ptr: 独占所指向的对象。
    • weak_ptr: 弱引用,指向stared_ptr所管理的对象。

    auto_ptr在C++11中被废弃,不再讨论。

    stared_ptr指针

    stared_ptr和unique_ptr都支持的操作

    stared_ptr sp; unique_ptr up 空智能指针,可以指向类型为T的对象
    p 将p用作一个条件判断,若p指向一个对象,则为true
    *p 解引用p,获得它指向的对象
    p→mem 等价于(*p).mem
    p.get() 返回p中保存的指针。要小心使用,若智能指针释放了其对象,返回的指针锁指向的对象也就消失了。
    swap(p, q)
    p.swap(q) 交换p和q中的指针

    shared_ptr中独有的操作

    make_shared(args) 返回一个shared_ptr,指向一个动态分配的类型为T的对象,使用args初始化此对象
    shared_ptrp(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的计数器为0,它就会自动释放自己所管理的对象。

    由于在最后一个shared_ptr销毁前内存都不会释放,保证shared_ptr在无用之后不再保留就非常重要。

    注意:如果将shared_ptr存放于一个容器中,而后不在需要全部的元素,而只是使用其中的一部分,要记得用erase删除不在需要的那些元素。

    shared_ptr与new结合使用

    不能将内置指针隐式转换为一个智能指针,必须使用直接初始化形式,如下:

    shared_ptr<int> p1 = new int(1024); // 错误
    shared_ptr<int> p2(new int(1024)); // 正确
    
    p2 = new int(1024); // 错误
    p2.reset(new int(1024)); // 正确
    

    注意:

    • 不要混用普通指针与智能指针!
    • 不要使用get初始化另一个智能指针或为智能指针赋值!

    容易出现同一块内存被delete两次,而出现core!

    如果使用智能指针,即使程序块过早结束,智能指针也能确保在内存不再需要时将其释放。而如果是普通指针,在new和delete之间发生了异常,且异常未捕获,则内存就永远不会被释放了。

    自定义删除器

    例子:

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

    智能指针陷阱

    智能指针使用规范:

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

    unique_ptr指针

    与shared_ptr不同的是,unique_ptr某个时刻只能有一个unique_ptr指向一个给定对象。

    unique_ptr操作

    unique_ptr 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)
    u.reset(nullptr) 如果提供了内置指针q,令u指向这个对象;否则将u值为空

    unique_ptr不支持普通的拷贝或赋值操作:

    unique_ptr<string> p1(new string("hello"));
    unique_ptr<string> p2(p1);  // 错误,unique_ptr不支持拷贝
    unique_ptr<string> p3;
    p3 = p2; // 错误,unique_ptr不支持赋值
    

    虽然不能通过拷贝或赋值unique_ptr,单可以通过调用release或reset将指针的所有权从一个(非const)unique_ptr转移给另一个unique:

    // 将所有权从p1转移给p2
    unique_ptr<string> p2(p1.release()); 
    unique_ptr<string> p3(new string("hello"));
    // 将所有权从p3转移给p2
    p2.reset(p3.release()); // reset释放了p2原来指向的内存
    

    weak_ptr

    weak_ptr是一种不可控制所指向对象生存期的智能指针,它指向由一个shared_ptr管理的对象。

    将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。

    weak_ptr的引入是为了解决shared_ptr存在的一个问题——循环引用 。

    weak_ptr操作

    weak_ptr w 空weak_ptr可以执行类型为T的对象
    weak_ptr 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.use_count()为0,返回true,否则返回false
    w.lock() 如果expired为true,返回一个空shared_ptr;否则返回一个指向w的对象的shared_ptr

    由于对象可能不存在,我们不能使用weak_ptr直接访问对象,而必须调用lock。此函数检查weak_ptr指向的对象是否仍存在。如果存在,lock返回一个指向共享对象的shared_ptr。

    经验之谈

    不要混用

    指针之间的混用,有时候会造成不可预知的错误,所以建议尽量不要混用。包括裸指针和智能指针以及智能指针之间的混用****

    裸指针和智能指针混用

    void fun() {
      auto ptr = new Type;
      std::shared_ptr<Type> t(ptr);
      
      delete ptr;
    }
    

    在上述代码中,将ptr所有权归shared_ptr所拥有,所以在出fun()函数作用域的时候,会自动释放ptr指针,而在函数末尾又主动调用delete来释放,这就会造成double delete,会造成segment fault

    智能指针混用

    void fun() {
      std::unique_ptr<Type> t(new Type);
      std::shared_ptr<Type> t1(t.get());
    }
    

    在上述代码中,将t关联的对象又给了t1,也就是说同一个对象被两个智能指针所拥有,所以在出fun()函数作用域的时候,二者都会释放其关联的对象,这就会造成double delete,会造成segment fault

    需要注意的是,下面代码在STL中是支持的:

    void fun() {
      std::unique_ptr<Type> t(new Type);
      std::shared_ptr<Type> t1(std::move(t));
    }
    

    不要管理同一个裸指针

    void fun() {
      auto ptr = new Type;
      std::unique_ptr<Type> t(ptr);
      std::shared_ptr<Type> t1(ptr);
    }
    

    在上述代码中,ptr所有权同时给了t和t1,也就是说同一个对象被两个智能指针所拥有,所以在出fun()函数作用域的时候,二者都会释放其关联的对象,这就会造成double delete,会造成segment fault

    避免使用get()获取原生指针

    void fun(){
      auto ptr = std::make_shared<Type>();
    
      auto a= ptr.get();
    
      std::shared_ptr<Type> t(a);
      delete a;
    }
    

    一般情况下,生成的指针都要显式调用delete来进行释放,而上述这种,很容易稍不注意就调用delete;非必要不要使用get()获取原生指针。****

    不要管理this指针

    class Type {
     private:
      void fun() {
        std::shared_ptr<Type> t(this);
      }
    };
    

    在上述代码中,如果Type在栈上,则会导致segment fault,堆上视实际情况(如果在对象在堆上生成,那么使用合理的话,是允许的)。****

    只管理堆上的对象

    void fun() {
       Type t;
       std::shared_ptr<Type> ptr(&t);
    };
    

    在上述代码中,t在栈上进行分配,在出作用域的时候,会自动释放。而ptr在出作用域的时候,也会调用delete释放t,而t本身在栈上,delete一个栈上的地址,会造成segment fault

    优先使用unique_ptr

    根据业务场景,如果需要资源独占,那么建议使用unique_ptr而不是shared_ptr,原因如下:

    • 性能优于shared_ptr
      • 因为shared_ptr在拷贝或者释放时候,都需要操作引用计数
    • 内存占用上小于shared_ptr
      • shared_ptr需要维护它指向的对象的线程安全引用计数和一个控制块,这使得它比unique_ptr更重量级

    使用make_shared初始化

    我们看下常用的初始化shared_ptr两种方式,代码如下:

    std::shared_ptr<Type> p1(new Type);
    std::shared_ptr<Type> p2 = std::make_shared<Type>();
    

    那么,上述两种方法孰优孰劣呢?我们且从源码的角度进行分析。

    第一种初始化方法,有两次内存分配:

    • new Type分配对象
    • 为p1分配控制块(control block),控制块用于存放引用计数等信息

    第一种初始化方式(new方式)共有两次内存分配操作,而第二种初始化方式(make_shared)只有一次内存申请,所以建议使用make_shared方式进行初始化。


    Reference:

  • 相关阅读:
    使用Docker在本地搭建Hadoop分布式集群
    微博推荐 第三个map 源码
    对象
    http无状态(stateless)
    理解http的无连接
    http响应报文之首部行
    http响应报文之状态行
    http响应报文
    http请求报文之首部行
    http请求之请求数据
  • 原文地址:https://www.cnblogs.com/cloudflow/p/16368158.html
Copyright © 2020-2023  润新知