智能指针shared_ptr,weak_ptr,unique_ptr

关于智能指针

template <class T>
class shared_ptr
{
public:
    T& operator*() const
    { return *px; }
 
    T* operator->() const
    { return px; }
 
    shared_ptr(T* p) : px(p) { }
 
private:
    T*    px;
    long* pn;
......
};
 
struct Foo
{
    ...
    void method(void);
};
 
shared_ptr<Foo> sp(new Foo());
 
Foo f(*sp);
 
sp->method(); //相当于执行px->method()

关键的就是两个操作符的重载,operator*()和operator->()。对于*的重载返回的是指针所指对象的引用,返回值应该是引用类型,因为这个对指针解引用这个操作是可以作为左值的。对于->的重载有一个比较tricky的地方,就是比如说上例中的sp->method()相当于执行px->method(),但是sp->按理说返回的只是px,也就是调用sp的操作符重载将会消耗一个->,那为什么对于px还是会有一个->呢?我们可以这样理解:箭头符号比较特别,得到的东西会继续使用箭头符号作用上去,可以理解为语言设计的一个规则。

关于迭代器

template <class T>
struct __list_node{
    void* prev;
    void* next;
    T data;
};
 
template <class T, class Ref, class Ptr>
struct __list_iterator{
    typedef __list_iterator<T, Ref, Ptr> self;
    typedef Ptr pointer;
    typedef Ref reference;
    typedef __list_node<T>* link_type;
    link_type node;
    bool operator==(const self& x) const { return node == x.node; }
    bool operator==(const self& x) const { return node == x.node; }
    reference operator*() const { return (*node).data; }
    pointer operator->() const { return &(operator*()); }
    ...
};

重要的是两个操作符的重载

std::shared_ptr和std::weak_ptr

通过指针保持对象共享,让多个std::shared_ptr引用对象占用同一个对象。当指针对象被另一个对象引用时,共享其所有权,每个指针对象有一个use_count,记录这个对象共享的指针对象个数。当最后的指针对象被销毁时,use_count抵达0,指针对象管理的指向被管理对象的指针也被delete。

#include<iostream>
#include <memory>
class Test
{
public:
    Test(){
        std::cout << "Test()" << std::endl;
    }
    ~Test(){
        std::cout << "~Test()" << std::endl;
    }
};
int main(){
    std::shared_ptr<Test> p1 = std::make_shared<Test>();
    std::cout << "1 ref:" << p1.use_count() << std::endl;
    {
        std::shared_ptr<Test> p2 = p1;
        std::cout << "2 ref:" << p1.use_count() << std::endl;
        std::shared_ptr<Test> p3 = p1;
        std::cout << "3 ref:" << p1.use_count() << std::endl;
    }
    std::cout << "4 ref:" << p1.use_count() << std::endl;
    system("pause");
    return 0;
}

std::make_shared封装了new操作符,实现了对象的实例化。
过程:执行std::shared_ptr< Test> p2 = p1和std::shared_ptr< Test> p3 = p1,引用对象各自加1,一次打印2和3,当大括号结束,p2和p3生命周期结束,计算器执行两次减1操作,p1引用计算重新回到1,当函数运行完之时,p1会被销毁,此时计算器是1,就会调用p1的析构函数delete之前用std:: make_shared创建的Test对象的指针。完成整个引用对象的管理。

问题:相互引用时对象释放异常

#include <iostream>
#include <memory>
class B;
class A{
public:
    A(){
        std::cout << "class A : constructor" << std::endl;
    }
    ~A(){
        std::cout << "class A : destructor" << std::endl;
    }
    void referB(std::shared_ptr<B> test_ptr) {
        _B_Ptr = test_ptr;
    }
private:
    std::shared_ptr<B> _B_Ptr;
};

class B{
public:
    B(){
        std::cout << "class B : constructor" << std::endl;
    }
    ~B() {
        std::cout << "class B : destructor" << std::endl;
    }
    void referA(std::shared_ptr<A> test_ptr){
        _A_Ptr = test_ptr;
    }
    std::shared_ptr<A> _A_Ptr;
};

int main()
{
    // test
    {
        std::shared_ptr<A> ptr_a = std::make_shared<A>();   //A引用计算器为1
        std::shared_ptr<B> ptr_b = std::make_shared<B>();   //B引用计算器为1
        ptr_a->referB(ptr_b);   // B引用计算器加1
        ptr_b->referA(ptr_a);   // A引用计算器加1
    }
    system("pause");
    return 0;
}

结果是A和B都没有调用析构函数

分析上面代码可知,std::shared_ptr< A>和std::shared_ptr< B>在接收std::make_shared实例化对象时,各自的引用计算都为1,而当执行ptr_a->referB(ptr_b)时,再次使用std::shared_ptr< B>,因此此时B的引用加1,即B的引用计算变成2;同理A的引用计算也变成2。当main函数退出前ptr_a和ptr_b引用计算均为2,main函数退出后引用计算均为1,构成相互引用。也就是会产生这样的情况:ptr_a等待ptr_b释放自己,这样ptr_a才能去释放ptr_b。同理,ptr_b也等待ptr_a释放自己,这样ptr_b才能去释放ptr_a。
可见,相互引用导致了相互释放冲突的问题,最终导致内存泄露发生

weak_ptr

将A和B中的函数成员由shared_ptr改为weak_ptr
运行结果:
可以看到ptr_a和ptr_b在main退出前,引用计算均为1,也就是说在A和B中对std::weak_ptr的引用不会引起引用计算加1,ptr_a和ptr_b可以正常释放,不会引起内存泄露。

另一个例子

可见std::weak_ptr拥有弱引用特性,不拥有对象,只有等到调用lock()函数时才会有可能拥有对象,std::weak_ptr有以下特性:

  • weak_ptr用于辅助shared_ptr的,只能指向shred_ptr对象,而不能直接指向实际对象本身,如weak_ptr<Abase> ptr (new Abase()); // 编译器会报错
  • 只有调用lock()创建std::shared_ptr时才会引用对象;
  • 可以使用lock函数来获取其对应的shared_ptr对象(lock()会创建shared_ptr指针,在该指针的作用域范围内,会使引用次数+1,出作用域,引用次数-1
  • std::weak_ptr不能操作对象,没有实现重载运算符
#include <iostream>
#include <memory>
 
using namespace std;
 
class base {
public:
    base(string name) : mName(name) {
        cout << "construct " << mName << endl;
    }
    virtual ~base() {
        cout << "deconstruct " << mName << endl;
    }
    void print() {
        cout << mName << " will print" << endl;
    }
public:
    string mName;
};
 
class Abase {
public:
    Abase(string name) : mName(name) {
        cout << "construct " << mName << endl;
    }
    virtual ~Abase() {
        cout << "deconstruct " << mName << endl;
    }
    void print() {
        cout << mName << " will print" << endl;
    }
public:
    string mName;
};
 
int main(int argc, char* argv[]) {
    {
        shared_ptr<Abase> A(new Abase("AAAAAAA"));
        // 或者用make_shared来构造
        // shared_ptr<Abase> A = maek_shared<Abase> ("AAAAAAA");
        shared_ptr<base> B(new base("BBBBBBB"));
        // 返回指向实际对象的普通指针,等于->
        Abase* ptr = A.get();
        ptr->print();
        cout << B.use_count() << endl;//1
        cout << A.use_count() << endl;//1
        weak_ptr<Abase> weaka = A;
        weak_ptr<base> weakb = B;
        {
            // 获取shared_ptr对象,这会使实际对象的引用次数+1,出作用域后引用计数-1
            shared_ptr<Abase> weakaLock = weaka.lock();
            weakaLock->print();
            cout << A.use_count() << endl;
        }
        if (!weakb.expired()) {
            // 将获取的shared_ptr对象赋值给临时变量,在这句话结束以后临时变量资源被回收,
            // 因此引用计数不会+1
            weakb.lock()->print();
            cout << B.use_count() << endl;
        }
        // 将weakb指向的对象清空
        weakb.reset();
        if (weakb.expired()) {
            cout << "weakb is invalid" << endl;
        }
        cout << B.use_count() << endl;//1
        cout << A.use_count() << endl;//1
    }
    system("pause");
    return 0;
}

unique_ptr

unique是装指针的容器,且拥有关联指针的唯一所有权。其内部没有引用计数use_count,当unique对象超出作用域时会自动析构,delete关联的堆内存上的对象。

规则

  1. 不能复制,必须用std::move
// 转移后 A_ptr2 == nullptr
std::unique_ptr<Abase> A_ptr1 = std::move(A_ptr2);
  1. 使用if( ptr1 == nullptr)判断对象是否为空,即是否关联原始指针
  2. 通过get()获取关联原始指针;通过reset()重置对象为空;通过release()返回获取关联指针,并释放关联指针所有权,此时unique_ptr对象为nullptr
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值