美文网首页
C++智能指针的初步研究

C++智能指针的初步研究

作者: 狗子孙 | 来源:发表于2018-09-14 13:43 被阅读5次

    C++中给一个指针用new新建了一个对象并分配内存空间后,需要在对象作用域结束之前用delete回收内存。而因为各种原因,delete可能会被漏掉,例如逻辑设计有误,导致delete之前先return了,或者delete之前有些操作抛出异常终止了,这样就会导致内存泄露。

    智能指针最先引入的是auto_ptr,然后在C++11标准中删除了auto_ptr,而新加入了shared_ptr,unique_ptr和weak_ptr。智能指针最基本也是最重要的行为,就是在该删除的时候,自动删除,而不依赖程序员去调用。接下来分别介绍这四个只能指针。

    auto_ptr

    auto_ptr是一个封装后的类,它有一个显示的构造函数,接收一个指针。不能将一个指针用赋值运算符赋值给它,只能用构造方法。可以通过->符号访问所指成员的成员变量和成员函数,通过*来获得所指向的对象。

    #include <iostream>
    #include <string>
    #include <memory>
    using namespace std;
    int main(void) {
        auto_ptr<string> ap(new string("hehe"));
        cout << ap->length() << endl; // 输出:4
        cout << *ap << endl;          // 输出:hehe
        return 0;
    }
    

    auto_ptr之所以被弃用,是因为对auto_ptr进行操作时,指针的所有权将被转移,从而原先的auto_ptr拥有空指针,而出错。例如下面:

    #include <iostream>
    #include <string>
    #include <memory>
    using namespace std;
    int main(void) {
        auto_ptr<string> ap(new string("hehe"));
        auto_ptr<string> ap2;
        ap2 = ap;
        cout << *ap2 << endl; // 输出:hehe
        cout << *ap << endl;  // 出错,ap此时为空
        return 0;
    }
    

    因此,在C++11标准中,auto_ptr被移除了,以三个新智能指针取而代之。

    unique_ptr

    unique_ptr和auto_ptr非常接近,但是它不允许赋值操作,也就不会发生所有权转移的问题。同时,unique_ptr相对更智能,对于返回的临时变量,如fun函数返回了一个unique_ptr,它可以接管其所有权。

    #include <iostream>
    #include <string>
    #include <memory>
    using namespace std;
    unique_ptr<string> fun()
    {
        return unique_ptr<string>(new string("haha"));
    }
    int main(void) {
        unique_ptr<string> up(new string("hehe"));
        unique_ptr<string> up2;
        // up2 = up;          // 错误:不能赋值
        up2 = move(up);       // 正确:可以用move赋值,赋值后up为空
        cout << *up2 << endl; // 输出:hehe
        up2 = fun();
        cout << *up2 << endl; // 输出:haha
        return 0;
    }
    

    shared_ptr

    shared_ptr和unique_ptr不同,它允许多个智能指针指向同一个对象,通过一个引用计数来统计仍在作用域内的智能指针数量,当数量为0即最后一个智能指针走出定义域的时候,它才会把删除内存空间。

    #include <iostream>
    #include <string>
    #include <memory>
    using namespace std;
    class Node2;
    class Node1 {
    public:
        ~Node1() {
            cout << "destructed" << endl;
        }
        shared_ptr<Node2> next;
    };
    class Node2 {
    public:
        ~Node2() {
            cout << "destructed" << endl;
        }
        shared_ptr<Node1> next;
    };
    int main(void) {
        shared_ptr<string> sp(new string("hehe"));
        cout << sp.use_count() << endl;      // 输出1
        shared_ptr<string> sp2 = sp;
        cout << sp2.use_count() << endl;     // 输出2
        shared_ptr<string> sp3 = sp;
        cout << sp.use_count() << endl;      // 输出3
        cout << *sp << endl                  // 输出hehe
            << *sp2 << endl                  // 输出hehe
            << *sp3 << endl;                 // 输出hehe
        sp3.reset();                         // sp3被销毁,sp和sp2不影响
        cout << *sp << endl                  // 输出hehe
            << *sp2 << endl                  // 输出hehe
            << sp2.use_count() << endl;      // 输出2,一个已销毁
        shared_ptr<Node1> n1(new Node1()); 
        shared_ptr<Node2> n2(new Node2());
        n1->next = n2; n2->next = n1;
        cout << n1->next.use_count() << endl; // 输出2
                                              // 最后没有输出 deleted
        return 0;
    }
    

    上面的代码显示了shared_ptr的作用和引用计数。但shared_ptr有一个问题在于,如果两个类互相以shared_ptr指向对方,引用计数为2。跳出作用域时,两个资源的引用计数减1,仍为1,故无法销毁,在上面代码的最后,没有输出"destructed"字样,说明没有调用析构函数,即shared_ptr没有销毁,产生死循环。

    weak_ptr

    针对上面shared_ptr存在的循环引用的问题,只要把Node1或Node2中的任意一个shared_ptr改成weak_ptr就可以了。程序的最后会输出两个destructed字样,说明两个实例都得到了析构。
    需要注意的是,weak_ptr不能直接访问资源,要用lock()方法得到一个shared_ptr才可以访问。

    #include <iostream>
    #include <string>
    #include <memory>
    using namespace std;
    class Node2;
    class Node1 {
    public:
        ~Node1() {
            cout << "destructed" << endl;
        }
        weak_ptr<Node2> next;
    };
    class Node2 {
    public:
        ~Node2() {
            cout << "destructed" << endl;
        }
        shared_ptr<Node1> next;
    };
    int main(void) {
        shared_ptr<string> sp(new string("hehe"));
        cout << sp.use_count() << endl;      // 输出1
        shared_ptr<string> sp2 = sp;
        cout << sp2.use_count() << endl;     // 输出2
        shared_ptr<string> sp3 = sp;
        cout << sp.use_count() << endl;      // 输出3
        cout << *sp << endl                  // 输出hehe
            << *sp2 << endl                  // 输出hehe
            << *sp3 << endl;                 // 输出hehe
        sp3.reset();                         // sp3被销毁,sp和sp2不影响
        cout << *sp << endl                  // 输出hehe
            << *sp2 << endl                  // 输出hehe
            << sp2.use_count() << endl;      // 输出2,一个已销毁
        shared_ptr<Node1> n1(new Node1()); 
        shared_ptr<Node2> n2(new Node2());
        n1->next = n2; n2->next = n1;
        auto sp_w = n1->next.lock();          // 从weak_ptr获取shared_ptr
        cout << n1->next.use_count() << endl; // 输出2
                                              // 最后输出2次deleted
        return 0;
    }
    

    智能指针的实现

    智能指针首先是一个模板类,将原始指针封装在里面,并在析构函数中对原始指针进行判断和销毁。智能指针最大的难点在于实现引用计数。

    下面的例子中,我们构造一个辅助模板类U_Ptr来操作原始指针,这个类包括了引用计数。SmartPtr模板类是U_Ptr模板类的友元,可以对它进行操作。初始化时,将U_Ptr的count值置为1,在拷贝构造函数和重载的赋值运算符中,对count进行增减操作。最后在析构函数中,根据count值判定是否执行真正的销毁操作。下面是一个较好的shared_ptr参考实现:

    // 本实现代码来自https://www.cnblogs.com/QG-whz/p/4777312.html
    // 模板类作为友元时要先有声明
    template <typename T>
    class SmartPtr;
    
    // 辅助类
    template <typename T>
    class U_Ptr
    {
    private:
        // 该类成员访问权限全部为private,因为不想让用户直接使用该类
        // 定义智能指针类为友元,因为智能指针类需要直接操纵辅助类
        friend class SmartPtr<T>;
        // 构造函数的参数为基础对象的指针
        U_Ptr(T *ptr) :p(ptr), count(1) {}
        // 析构函数
        ~U_Ptr() { delete p; }
        // 引用计数
        int count;
        // 基础对象指针
        T *p;
    };
    
    // 智能指针类
    template <typename T>
    class SmartPtr
    {
    public:
        // 构造函数
        SmartPtr(T *ptr) :rp(new U_Ptr<T>(ptr)) {}
        // 复制构造函数
        SmartPtr(const SmartPtr<T> &sp) :rp(sp.rp) { ++rp->count; }
        // 重载赋值操作符
        SmartPtr& operator=(const SmartPtr<T>& rhs) {    
            ++rhs.rp->count;       // 首先将右操作数引用计数加1,
            if (--rp->count == 0)  // 然后将引用计数减1,理解为原有的对象-1了,可以应对自赋值
                delete rp;
            rp = rhs.rp;
            return *this;
        }
        // 重载*操作符
        T & operator *()
        {
            return *(rp->p);
        }
        // 重载->操作符  
        T* operator ->()
        {
            return rp->p;
        }
        // 析构函数
        ~SmartPtr() {        
            if (--rp->count == 0)  // 当引用计数减为0时,删除辅助类对象指针,从而删除基础对象
                delete rp;
            else
                cout << "还有" << rp->count << "个指针指向基础对象" << endl;
        }
    private:
        // 辅助类对象指针
        U_Ptr<T> *rp;
    };
    

    参考

    C++ 引用计数技术及智能指针的简单实现

    相关文章

      网友评论

          本文标题:C++智能指针的初步研究

          本文链接:https://www.haomeiwen.com/subject/nwizwftx.html