C++“智能指针”实践与python的weakref

这两天复习C++ Primer时,在“复制控制”这一章看到管理指针成员,在没使用标准库的情况下自己创建一个智能指针。不过书中还是建议使用标准库unique_ptr、shared_ptr、weak_ptr实现,减少程序员内存管理问题的工作。

指针成员默认具有与指针对象同样的行为,但通过不同的复制控制策略,可以为指针成员实现不同的行为。多数C++类采用以下三种方法管理指针成员:

(1)指针成员采用常规指针行为。这样的类具有指针的所有缺陷但不需要自定义特殊的复制控制函数;

(2)类实现自定义的“智能指针”行为。指针所指向的对象是共享的,但能够防止悬垂指针问题;

(3)类采取值型行为。类的指针所指向的对象是唯一,复制控制采取深拷贝策略;

为了阐明方法(2),接下来设计一个简单的类Simple.

class Simple{
public:
    Simple(int *p, int i):ptr(p), val(i) {};
    
    int *get_ptr() const { return ptr;};
    int get_int() const { return val;};
    
    void set_ptr(int *p) { ptr = p;};
    void set_int(int i) { val = i;};

    int get_ptr_val() const { return *ptr;};
    void set_ptr_val(int val) const { *ptr = val};

private:
    int *ptr;
    int val;
};

因为Simple类没有定义复制构造函数,所以复制一个Simple对象将简单的复制两个数据成员:

int obj = 0;
Simple s1(&obj, 42);
Simple s2(s1);

s1.set_ptr_val(42);
s2.get_ptr_val();    // return 42

s1与s2的指针指向同一个对象,在任意一个对象上调用set_ptr_val()都会改变所指向的基础对象的值。

这种简单的指针型行为会使用户面临潜在的问题:悬垂指针。

int *ip = new int(42);
Simple s1(ip, 10);
Simple s2(s1);

delete s1;
s2.set_ptr_val(0); // disater: the object to which s2 points was freed!

解决指针悬垂问题的一个通用方法是采用引用计数。我们将智能指针单独设计成一个智能指针类,它使用一个计数器与类指向的元数据对象相关联,计数器跟踪该类有多少个对象共享同一个指向元数据的指针。当引用计数为0时,删除元数据对象。

定义一个单独的具体类来封装引用计数和相关指针:

class U_ptr{
    friend class Simple;

    int *ip;
    size_t use_count;
    
    U_ptr(int *p): ip(p), use_count(1) {}
    ~U_ptr() {delete ip;}
};

U_ptr类仅仅保存指向元数据的指针和引用计数。使用U_ptr改写Simple类:

class Simple{
public:
    Simple(int *p, int i): ptr(new U_ptr(p)), val(i) {}
    Simple(const Simple &orig): ptr(orig.ptr), val(orig.val) {}

    Simple& operator=(const Simple&);
    ~Simple() {
        if(--ptr->use_count == 0)
            delete ptr;
    }

private:
    U_ptr *ptr;
    int val;
};

Simple& Simple::operator= (const Simple& rhs)
{
    ++rhs.ptr->use_count;
    if( --ptr->use_count == 0)
        delete ptr;
    
    ptr = rhs.ptr;
    val = rhs.val;
    return *this;
}

Simple的构造函数执行后,Simple对象含有一个指向新U_ptr对象的指针,新的U_ptr对象中引用计数为1,表示只有一个Simple对象指向它。

复制构造函数从形参复制成员并增加引用计数值。复制构造函数执行完毕后,新创建的Simple对象与原对象指向同一个U_ptr对象,该U_ptr对象的引用计数加1。

析构函数将检查U_ptr对象的引用计数,如果为0则删除U_ptr指针,删除该指针将引起U_ptr析构函数的调用,最终删除元数据对象。

赋值操作符稍微复杂点,首先将右操作数的引用计数加1,然后将左操作数(this)的引用计数减1并检查该计数是否为0,是则直接删除之前指向的U_ptr对象。最后将右操作数rhs的数据成员依次复制过来。

但是上述基于引用计数的指针管理方法还是有弊端的,比如循环引用问题,在树、图等数据结构中是常见的。比如在树结构中,父节点和子节点相互引用,导致每个对象的引用计数都不为0. 此时采用标准库中的std::weak_ptr可以解决问题。同理在python中可以参考弱引用的方法python中的弱引用weakref

发布了94 篇原创文章 · 获赞 140 · 访问量 33万+

猜你喜欢

转载自blog.csdn.net/fang_chuan/article/details/91976414