C++学习2

std::auto_ptr

std::auto_ptr指针在C++11标准中被移除了,可以使用unique_ptr来代替,功能上是相同的,unique_ptr相比较auto_ptr而言,提升了安全性(没有浅拷贝),增加了特性(delete析构)和对数组的支持。
这个类模版提供了有限的垃圾回收机制,通过将一个指针保存在auto_ptr对象中,当auto_ptr对象析构时,这个对象所保存的指针也会被析构掉。
auto_ptr对象拥有其内部指针的所有权。这意味着auto_ptr对其内部指针的释放负责,既当自身被释放时,会在析构函数中自动的调用delete,从而释放内部指针的内存。
解释:

  • 正因为如此,不能有两个auto_ptr对象拥有同一个内部指针的所有权,因为有可能在某个时机,两者均会尝试析构这个内部指针。
  • 当两个auto_ptr对象之间发生赋值操作时,内部指针被拥有的所有权会发生转移,这意味着这个赋值的右者对象会丧失该所有权,不在指向这个内部指针(其会被设置成null指针)。

到这里,我们来看一下auto_ptr的提供的接口和使用方法:

  • constructor
  • destructor
  • get
  • operator*
  • operator->
  • operator=
  • release
  • reset
  • conversion operators

其中构造只的说一下:

  • std::auto_ptr::auto_ptr

解释:auto_ptr的构造的参数可以是一个指针,或者是另外一个auto_ptr对象。

  • 当一个新的auto_ptr获取了内部指针的所有权后,之前的拥有者会释放其所有权
  1. auto_ptr的构造及所有权的转移
#include 
#include 

int main(int argc, char const *argv[]){
    // 通过指针进行构造
    std::auto_ptr aptr1(new int(3));
    std::printf("aptr1 %p : %d\r\n", aptr1.get(), *aptr1);
    // 这样会编译出错,因为auto_ptr的构造有关键字explicit
    // explicit 关键字表示调用构造函数是不能使用隐式赋值,而必须是显示调用
    // std::auto_ptr aptr2 = new int(3);
    // 可以用其他的auto_ptr指针进行初始化
    std::auto_ptr aptr2(new int(44));
    std::printf("aptr2 %p : %d\r\n", aptr2.get(), *aptr2);
    std::printf("===========================\n");
    // aptr2 会被释放
    aptr1 = aptr2;
    std::printf("aptr1 %p : %d\r\n", aptr1.get(), *aptr1);
    // 内存访问出错,直接0xc05,因为aptr2已经释放了其所有权
    // std::printf("aptr2 %p : %d\r\n", aptr2.get(), *aptr2);
    return 0;
}
  1. auto_ptr析构及资源的自动释放
#include 
#include 

int main(int argc, char const *argv[]){
    int * pNew = new int(3);
    {
        std::printf("pNew:%d\n", *pNew);
        //当释放aptr时,pNew 也会被释放了(注意,要出作用域才释放)
        std::auto_ptr aptr(pNew);
        std::printf("aptr:%d\n", *aptr);
    }
    std::printf("pNew:%d\n", *pNew);
    return 0;
}
  • 这里显然,当出了块作用域之后,aptr对象会自动调用析构,然后在析构中会自动的delete其内部指针,也就是出了这个作用域后,其内部指针就被释放了。
  • 当然上面这种写法是不推荐的,因为我们这里本质上就是希望不去管理指针的释放工作,上面的写法就又需要成员自己操心指针的问题,也就是使用智能指针要避免出现指针的直接使用!

在这里可以使用前调用release,从而放弃其内部指针的使用权,但是同样这么做违背了智能指针的初衷。

#include 
#include 

int main(int argc, char const *argv[]){
    int * pNew = new int(3);
    {
        std::printf("pNew:%d\n", *pNew);
        std::auto_ptr aptr(pNew);
        // aptr 放弃其内部指针的使用权
        int* p = aptr.release();
        // std::printf("aptr:%d\n", *aptr);
    }
    std::printf("pNew:%d\n", *pNew);
    return 0;
}
  1. 分配新的指针所有权
  • 可以调用reset来重新分配指针的所有权,reset中会先释放原来的内部指针的内存,然后分配新的内部指针。
#include 
#include 

int main(int argc, char const *argv[]){
    int * pNew = new int(3);
    {
        std::printf("point:%p, pNew:%d\n", pNew, *pNew);
        std::auto_ptr aptr(pNew);
        std::printf("point:%p, aptr:%d\n", aptr.get(), *aptr);
        // aptr 重新分配指针的所有权, pNew的内容被清除
        aptr.reset(new int(555));
        std::printf("====================\n");
        std::printf("point:%p, aptr:%d\n", aptr.get(), *aptr);
        std::printf("point:%p, pNew:%d\n", pNew, *pNew);
    }
    std::printf("====================\n");
    std::printf("point:%p, pNew:%d\n", pNew, *pNew);
    return 0;
}
  1. = 运算符的使用
#include 
#include 

int main(int argc, char const *argv[]){
    {
        std::auto_ptr p1;
        std::auto_ptr p2;
        p1 = std::auto_ptr(new int(33));
        std::printf("point:%p, pNew:%d\n", p1.get(), *p1);
        *p1 = 4;
        std::printf("point:%p, pNew:%d\n", p1.get(), *p1);
        // p1 会被释放
        p2 = p1;
        // std::printf("point:%p, pNew:%d\n", p1, *p1);
        std::printf("point:%p, pNew:%d\n", p2, *p2);
    }
    return 0;
}

auto_ptr存在的问题

为什么C11标准会不让使用auto_ptr,原因是其使用有问题。
作为参数传递会存在问题

  • 因为有拷贝构造函数和赋值的情况下,会释放原有的对象的内部指针,所有当有函数使用的是auto_ptr时,调用后会导致原来的内部指针释放。
#include 
#include 

void func_test(std::auto_ptr p){
    std::printf("point:%p, p:%d \n", p.get(), *p);
}

int main(int argc, char const *argv[]){
    std::auto_ptr p1 = std::auto_ptr(new int(44));
    func_test(p1);
    // 这里的调用会出错,因为拷贝构造函数的存在,p1实际上已经释放了其内部指针的所有权了
    // std::printf("point:%p, p1:%d \n", p1.get(), *p1);
    return 0;
}

不能使用vector数组

#include 
#include 
#include 

int main(int argc, char const *argv[]){
    std::vector> ary;
    std::auto_ptr p(new int(33));
    // 用不了
    ary.push_back(p);
    return 0;
}

unique_ptr

前面我们讲解了auto_ptr的使用及为什么会被C++11标准抛弃,接下来,我们学习unique_ptr的使用
unique_ptr提供一下操作:

  • (constructor)
  • (destructor)
  • operator=
  • get
  • get_deleter
  • operator bool
  • release
  • reset
  • swap
  • operator*
  • operator->
  • operator[]
  1. 析构函数

从根源杜绝了auto_ptr作为参数传递的写法

#include 
#include 

int main(int argc, char const *argv[]){
    // 这样构造是可以的
    std::unique_ptr p1(new int(3));
    // 空构造也是可以的
    std::unique_ptr p2;
    // 下面三种写法会报错
    // std::unique_ptr p3 = p1; // 需要拷贝构造
    // std::unique_ptr p4(p1);    // 需要拷贝构造
    // p2 = p1;         // 需要=运算符重载
    return 0;
  1. reset

reset的用法和auto_ptr是一致的

#include 
#include 

int main(int argc, char const *argv[]){
    int *pNew = new int(4);
    int *p = new int(1);
    {
        std::printf("pNew:%d, p:%d\n", *pNew, *p);
        // pNew 会被释放
        std::unique_ptr uptr(pNew);
        uptr.reset(p);    
        // *p = 444;
        std::printf("pNew:%d, p:%d, uptr:%d \n", *pNew, *p, *uptr);
        // uptr 出作用域会被释放,p也会被置空,但p指针没有被释放
    }
    //指针没有被释放
    *p = 99;
    std::printf("pNew:%d, p:%d\n", *pNew, *p);
    return 0;
}
  1. release

release与reset一样,也不会释放原来的内部指针,只是简单的将自身置空。

#include 
#include 

int main(int argc, char const *argv[]){
    int *pNew = new int(7);
    int *p = NULL;
    {
        std::unique_ptr uptr(pNew);
        std::printf("pNew point:%p, v:%d\n", pNew, *pNew);
        std::printf("uptr point:%p, v:%d\n", uptr.get(), *uptr);
        p = uptr.release();
        std::printf("pNew point:%p, v:%d\n", pNew, *pNew);
        // 报错 uptr 被置空
        // std::printf("pNew point:%p, v:%d\n", uptr.get(), *uptr);
        std::printf("p point:%p, v:%d\n", p, *p);
    }
    std::printf("pNew point:%p, v:%d\n", pNew, *pNew);
    std::printf("p point:%p, v:%d\n", p, *p);
    return 0;
}
  1. move

但是多了个move的用法
因为unique_ptr不能将自身对象内部指针直接赋值给其他unique_ptr,所以这里可以使用std::move()函数,让unique_ptr交出其内部指针的所有权,而自身置空,内部指针不会释放。

#include 
#include 

int main(int argc, char const *argv[]){
    int *p = new int(99);
    std::unique_ptr uptr(p);
    std::printf("uptr point:%p, v:%d\n", uptr.get(), *uptr);
    std::unique_ptr uptr2 = std::move(uptr);
    std::printf("p point:%p, v:%d\n", p, *p);
    // 报错 uptr 被置空
    // std::printf("uptr point:%p, v:%d\n", uptr.get(), *uptr);
    std::printf("uptr2 point:%p, v:%d\n", uptr2.get(), *uptr2);
    return 0;
}
  1. 数组

可以采用move的方式来使用数据。
直接使用仍然会报错:

#include 
#include 
#include 

int main(int argc, char const *argv[]){
    std::vector> Ary;
    std::unique_ptr p(new int(3));
    // 报错,不能直接赋值
    // Ary.push_back(p);
    Ary.push_back(std::move(p));
    // 报错 p已被置空
    // std::printf("p:%d\n", *p);
    return 0;
}

shareed_ptr与weak_ptr

shared_ptr是带引用计数的智能指针

1、构造

其初始化多了一个写法:std::make_shared

#include 
#include 

int main(int argc, char const *argv[]){
    int *p = new int(11);
    std::shared_ptr sptr1(p);
    std::shared_ptr sptr2(new int(5));
    std::shared_ptr sptr3 = sptr2;
    std::shared_ptr sptr4 = std::make_shared(4);
    std::printf("sptr1 point:%p, v:%d\n", sptr1.get(), *sptr1);
    std::printf("sptr2 point:%p, v:%d\n", sptr2.get(), *sptr2);
    std::printf("sptr3 point:%p, v:%d\n", sptr3.get(), *sptr3);
    std::printf("sptr4 point:%p, v:%d\n", sptr4.get(), *sptr4);
    return 0;
}

这里显然可以看到有引用计数的存在
通过修改上面例子种的是sptr3的作用域之后,shared_ptr对应的引用计数的值减少了。

#include 
#include 

int main(int argc, char const *argv[]){
    std::shared_ptr sptr2(new int(5));
   { 
        std::shared_ptr sptr3 = sptr2;
        *sptr3 = 9;
        std::printf("sptr2 point:%p, v:%d\n", sptr2.get(), *sptr2);
        std::printf("sptr3 point:%p, v:%d\n", sptr3.get(), *sptr3);
    }
    std::printf("sptr2 point:%p, v:%d\n", sptr2.get(), *sptr2);
    return 0;
}

注意事项

  • 如果用同一个指针去初始化两个shared_ptr时,则引用计数仍然会出错:
#include 
#include 

int main(int argc, char const *argv[]){
    int *p = new int(44);
    {
        std::shared_ptr sptr1(p);
        {
            // 运行报错,指针p 会被释放 
            std::shared_ptr sptr2(p);
        }
        // 出作用域,会释放指针p,会导致p被重复释放
    }
    return 0;
}

显然出了最里面的作用域之后,sptr2对象就已经释放了,此时,对于sptr2来说,p的引用计数为0,所以p被释放,但是实际上sptr1还存在,所以再释放sptr1时,就会报错。

#include 
#include 

int main(int argc, char const *argv[]){
    {
        std::shared_ptr sptr1(new int(5));
        {
            std::shared_ptr sptr2(sptr1);
            *sptr2 = 9;
            std::printf("sptr2 point:%p, v:%d\n", sptr2.get(), *sptr2);
        }
        std::printf("sptr2 point:%p, v:%d\n", sptr1.get(), *sptr1);

    }
    return 0;
}

share_ptr最多的问题时存在循环引用的问题:
如果两个类的原始指针的循环使用,那么会出现重复释放的问题。
这里,delete pSon会出现循环调用父子类的析构函数,问题很大。

#include 
#include 

class CSon;
class CPerson;


class CPerson{
public:
    CPerson(){}
    void Set(CSon *pSon){
        m_pson = pSon;
    }
    ~CPerson(){
        if(m_pson != nullptr){
            delete m_pson;
            m_pson = nullptr;
        }
    }
public:
    CSon *m_pson;
};
class CSon{
public:
    CSon(){}
    void Set(CPerson *pParent){
        m_pParent = pParent;
    }
    ~CSon(){
        if(m_pParent != nullptr){
            delete m_pParent;
            m_pParent = nullptr;
        }
    }
public:
    CPerson *m_pParent;
};

int main(int argc, char const *argv[]){
    CPerson *pPer = new CPerson();
    CSon *pSon = new CSon();
    
    pPer->Set(pSon);
    pSon->Set(pPer);

    // delete pSon;
    return 0;
}

因此,这里考虑使用引用计数的shared_ptr来实现。

#include 
#include 

class CSon;
class CPerson;

class CPerson{
public:
    CPerson(){}
    void Set(std::shared_ptr pSon){
        m_pson = pSon;
    }
    ~CPerson(){}
public:
    std::shared_ptr m_pson;
};
class CSon{
public:
    CSon(){}
    void Set(std::shared_ptr pParent){
        m_pParent = pParent;
    }
    ~CSon(){}
public:
    std::shared_ptr m_pParent;
};

int main(int argc, char const *argv[]){
    // CPerson *pPer = new CPerson();
    // CSon *pSon = new CSon();
    // 循环的引用,会出现析构异常
    // weak_ptr 弱指针
    {
        // std::shared_ptr shared_parent(pPer);
        // std::shared_ptr shared_son(pSon);
        std::shared_ptr shared_parent(new CPerson());
        std::shared_ptr shared_son(new CSon());
        shared_parent->Set(shared_son);
        shared_son->Set(shared_parent);
        std::printf("pPer: %d\n", shared_parent.use_count());
        std::printf("pSon: %d\n", shared_son.use_count());
    }
    return 0;
}

这里在出作用域后发现,实际上两个对象均为被销毁
最后两者的引用计数均为1,原因是除了块作用域之后,两个shared_parent和shared_son均会析构,在这两个智能指针的内部,均会先去判断对应的内部指针引用次数-1是否为0,显然,这里相互引用的情况下,引用次数初值为2,减1后值为1,所以两个指针均不会被释放。
这里,其实只需要一个释放了,另外一个也能跟着释放,可以采用弱指针,即认为的迫使其中一个引用计数为1,从而打破闭环。
这里只需要将上例子中的任意一个强指针改为弱指针即可。

#include 
#include 

class CSon;
class CPerson;

class CPerson{
public:
    CPerson(){}
    void Set(std::shared_ptr pSon){
        m_pson = pSon;
    }
    ~CPerson(){}
public:
    std::shared_ptr m_pson;
};
class CSon{
public:
    CSon(){}
    void Set(std::shared_ptr pParent){
        m_pParent = pParent;
    }
    ~CSon(){}
public:
    std::weak_ptr m_pParent;
};

int main(int argc, char const *argv[]){
    // CPerson *pPer = new CPerson();
    // CSon *pSon = new CSon();
    // 循环的引用,会出现析构异常
    // weak_ptr 弱指针
    {
        // std::shared_ptr shared_parent(pPer);
        // std::shared_ptr shared_son(pSon);
        std::shared_ptr shared_parent(new CPerson());
        std::shared_ptr shared_son(new CSon());
        shared_parent->Set(shared_son);
        shared_son->Set(shared_parent);
        std::printf("pPer: %d\n", shared_parent.use_count());
        std::printf("pSon: %d\n", shared_son.use_count());
    }
    return 0;
}

最后的结果,此时,两个内部指针均会得到释放。

强弱指针计数器增减分析

前4个字节是虚表指针
中间两个4字节分别是内部对象指针计数器和自身的计数其
最后4字节是内部对象指针。
到这里就shared_ptr与weak_ptr的代码分析的差不多了
最后说一下计数器增减的规则
初始化及增加的情形

  • 当创建一个新的shared_ptr时,内部对象计数器和自身的计数器均置1
  • 当将另外一个shared_ptr赋值给新的shared_ptr时,内部对象计数器+1,自身计数器不变。
  • 当将另外一个shared_ptr赋值给新的weak_ptr时,内部对象计数器不变,自身计数器+1.
  • 当从weak_ptr获取一个shared_ptr时,内部对象计数器+1,自身计数器不变。

减少的情形

  • 当一个shared_ptr析构时,内部对象计数器-1,当内部对象计数器减为0时,则释放内部对象,并将自身计数器-1
  • 当一个weak_ptr析构时,自身计数器-1,当自身计数器减为0时,则释放自身_Ref_count* 对象

那么就可以自己来模拟强弱指针,并修改成模版。
思考

  • 强指针构造,析构,=赋值,拷贝构造等情况下,计数器的变化
  • 弱指针构造,析构,=赋值,拷贝构造等情况下,计数器的变化
  • 弱指针提升为强指针时,计数器的变化

强指针直接构造(拿原始指针构造)时

  • 初始化_Ty *_Ptr
  • 创建_Ref_count对象
  • _Ref_count_base对象构造时,分别为_Uses = 1 并且 _Weaks = 1

你可能感兴趣的:(C++,c++,学习)