C++ 智能指针的实现

C++ 智能指针的实现

Sep 5, 2017
Coding
C++, 智能指针

1. shared_ptr 的特性 #

shared_ptr内部维护一个引用计数,当创建、copy、销毁时,引用计数都会变化

(1) 拷贝构造函数 p(q),会递增q的引用,p和q指向同一个对象 #

shared_ptr<string> q = make_shared<string>("smartptr");
shared_ptr<string> p(q); //递增q的引用计数,p和q指向同一个对象
std::cout << "p use_count=" << p.use_count() << std::endl; // 2
std::cout << "q use_count=" << q.use_count() << std::endl; // 2

(2) 赋值构造函数 p=q,会递增q的引用,递减p的引用 #

class Man
{
public:
    Man(string name) :_name(name) { std::cout << _name << ": Create" << std::endl; };
    ~Man() { std::cout << _name << ": Delete" << std::endl; };
private:
    string _name;
};

shared_ptr<Man> q = make_shared<Man>("Man_q");
shared_ptr<Man> p = make_shared<Man>("Man_p");
std::cout << "p use_count=" << p.use_count() << std::endl; // 1
std::cout << "q use_count=" << q.use_count() << std::endl; // 1

p = q; //递增q的引用计数,递减p的引用,当p引用为0时,释放其内部的对象。p和q指向同一个对象
std::cout << "p use_count=" << p.use_count() << std::endl; // 2
std::cout << "q use_count=" << q.use_count() << std::endl; // 2

上面这个示例代码,当 执行 p = q时,

  • (1) 递减p的引用计数,p之前的引用计数为1,递减后为0
  • (2) 如果p的引用计数为0,则释放p中原有的对象,此时会调用Man的析构,打印"Man_p :Delete"
  • (3) 递增q的引用计数,即变为2
  • (4) 将 p 指向 q

2. 实现一个智能指针SmartPtr #

理解了上面的过程之后,就可以动手实现一个智能指针

template <class _Ty>
class SmartPtr
{
public:
    SmartPtr(_Ty* ptr) :_refptr(new RefPtr(ptr)) {
    };

    SmartPtr(const SmartPtr& obj){ // p = q
        if (this != &obj){
            if (_refptr){
                *(_refptr->_count) -= 1; // 递减p的引用计数
                if (*(_refptr->_count) == 0){ // 如果p的引用计数为0,则删除
                    delete _refptr;
                }
            }

            _refptr = obj._refptr; // 将p指向q
            *(_refptr->_count) += 1; // 递增q的引用计数
        }
    }

    SmartPtr& operator=(const SmartPtr& obj){ // 同上
        if (this != &obj){
            if (_refptr){
                *(_refptr->_count) -= 1;
                if (*(_refptr->_count) == 0){
                    delete _refptr;
                }
            }

            _refptr = obj._refptr;
            *(_refptr->_count) += 1;
        }
        return *this;
    }

    ~SmartPtr(){
        *(_refptr->_count) -= 1; // 析构时,递减引用计数
        if (*(_refptr->_count) == 0){ // 如果引用计数为0,则删除
            delete _refptr;
        }
    }

int use_count() { return *(_refptr->_count); }

private:
    // 辅助类,用于保存引用计数 和 实际对象
    // 之所以需要使用辅助类,而不是直接用int _count和 _Ty* 成员变量
    // 是为了能否封装住 _count和_Ty* 这些变量,对外不可见
    class RefPtr { 
        friend class SmartPtr; 
        RefPtr(_Ty* ptr) :_ptr(ptr) { _count = new int(1); };
        ~RefPtr() { if (_ptr) delete _ptr; delete _count; };
        int* _count;
        _Ty* _ptr;
    };
    RefPtr* _refptr;
};

这里有几个细节

  • (1) 将SmartPtr声明为RefPtr的友元之后,SmartPtr就可以访问RefPtr的私有变量
  • (2) RefPtr全部为private,可以很好的封装不对外可见
  • (3) _count 声明为int* 指针,这样,在拷贝构造和赋值构造中,就可以对 const SmartPtr& obj 这个obj对象的count也进行改变,因为this->_refptr->_count和 obj的count指向同个内存区;再者对this->_refptr->_count 做递减时,也会影响到其他指向this的智能指针的引用计数

使用示例如下

SmartPtr<Man> man1(new Man("Man1"));

{
    std::cout << "Cnt1:" << man1.use_count() << std::endl; // 1

    SmartPtr<Man> man2(man1); //man2 是man1的copy
    std::cout << "Cnt1:" << man1.use_count() << std::endl; // 2
    std::cout << "Cnt2:" << man2.use_count() << std::endl; // 2

    {
        SmartPtr<Man> man3(new Man("Man3"));
        std::cout << "Cnt1:" << man1.use_count() << std::endl; // 2
        std::cout << "Cnt2:" << man2.use_count() << std::endl; // 2
        std::cout << "Cnt3:" << man3.use_count() << std::endl; // 1

        man3 = man1; //man3先析构了Man3, 再指向了man1, 同时引用+1 ; Man3: Delete
        std::cout << "Cnt1:" << man1.use_count() << std::endl; // 3
        std::cout << "Cnt2:" << man2.use_count() << std::endl; // 3
        std::cout << "Cnt3:" << man3.use_count() << std::endl; // 3
    }

    std::cout << "Cnt1:" << man1.use_count() << std::endl; // 2 因为man3 出了作用域,析构了
    std::cout << "Cnt2:" << man2.use_count() << std::endl; // 2

}

std::cout << "Cnt1:" << man1.use_count() << std::endl; // 1 因为man2 出了作用域,析构了

输出如下

Man1: Create
Cnt1:1
Cnt1:2
Cnt2:2
Man3: Create
Cnt1:2
Cnt2:2
Cnt3:1
Man3: Delete
Cnt1:3
Cnt2:3
Cnt3:3
Cnt1:2
Cnt2:2
Cnt1:1