再论智能指针(十六)

        我们在之前已经介绍过智能指针了,为什么我们还在这块再提出来呢?在之前的单链表(LinkList)的实现中,我们有使用了原生指针,那么此时我们用智能指针来代替原生指针是否可以呢?我们来试试,如下

#ifndef LINKLIST_H
#define LINKLIST_H

#include "List.h"
#include "Exception.h"
#include "SmartPointer.h"

namespace DTLib
{

template < typename T >
class LinkList : public List<T>
{
protected:
    struct Node : public Object
    {
        T value;
        // Node* next;
        SmartPointer<Node> next;
    };

    mutable struct : public Object
    {
        char reserved[sizeof(T)];
        // Node* next;
        SmartPointer<Node> next;
    } m_header;

    int m_length;
    int m_step;
    // Node* m_current;
    SmartPointer<Node> m_current;

    //Node* position(int i) const
    Node* position(int i) const
    {
        // Node* ret = reinterpret_cast<Node*>(&m_header);
        SmartPointer<Node> ret = reinterpret_cast<Node*>(&m_header);

        for(int p=0; p<i; p++)
        {
            ret = ret->next;
        }

        return ret.get();
    }

    //virtual Node* create()
    virtual SmartPointer<Node> create()
    {
        return new Node();
    }

    virtual void destroy(Node* pn)
    {
        delete pn;
    }
public:
    LinkList()
    {
        m_header.next = NULL;
        m_length = 0;
        m_step = 1;
        m_current = NULL;
    }
    bool insert(const T& e)
    {
        return insert(m_length, e);
    }

    bool insert(int i, const T& e)
    {
        bool ret = ((0 <= i) && (i <= m_length));

        if( ret )
        {
            // Node* node = create();
            SmartPointer<Node> node = create();

            if( node.isNull() )
            {
                // Node* current = position(i);
                SmartPointer<Node> current = position(i);

                node->value = e;
                node->next = current->next;
                current->next = node;

                m_length++;
            }
            else
            {
                THROW_EXCEPTION(NoEnoughMemoryException, "No memory to insert new element ...");
            }
        }

        return ret;
    }

    bool remove(int i)
    {
        bool ret = ((0 <= i) && (i < m_length));

        if( ret )
        {
            // Node* current = position(i);
            // Node* toDel = current->next;
            SmartPointer<Node> current = position(i);
            SmartPointer<Node> toDel = current->next;

            if( m_current == toDel )
            {
                m_current = toDel->next;
            }

            current->next = toDel->next;

            m_length--;

            // destroy(toDel);
        }

        return ret;
    }

    bool set(int i, const T& e)
    {
        bool ret = ((0 <= i) && (i < m_length));

        if( ret )
        {
            position(i)->next->value = e;
        }

        return ret;
    }

    T get(int i) const
    {
        T ret;

        if( get(i, ret) )
        {
            return ret;
        }
        else
        {
            THROW_EXCEPTION(IndexOutOfBoundsException, "Invaild parameter i to get element ...");
        }
    }

    bool get(int i, T& e) const
    {
        bool ret = ((0 <= i) && (i < m_length));

        if( ret )
        {
            e = position(i)->next->value;
        }

        return ret;
    }

    int find(const T& e) const
    {
        int ret = -1;
        int i = 0;
        // Node* node = m_header.next;
        SmartPointer<Node> node = m_header.next;

        while( node.isNull() )
        {
            if( node->value == e )
            {
                ret = i;
                break;
            }
            else
            {
                node = node->next;
                i++;
            }
        }

        return ret;
    }

    int length() const
    {
        return m_length;
    }

    void clear()
    {
        // while( m_header.next )
        while( m_header.next.isNull() )
        {
            // Node* toDel = m_header.next;
            SmartPointer<Node> toDel = m_header.next;

            m_header.next = toDel->next;

            m_length--;

            // destroy(toDel);
        }
    }

    bool move(int i, int step = 1)
    {
        bool ret = (0 <= i) && (i < m_length) && (step > 0);

        if( ret )
        {
            m_current = position(i)->next;
            m_step = step;
        }

        return ret;
    }

    bool end()
    {
        // return (m_current == NULL);
        return m_current.isNull();
    }

    T current()
    {
        if( !end() )
        {
            return m_current->value;
        }
        else
        {
            THROW_EXCEPTION(INvalidOPerationException, "No value at current position ...");
        }
    }

    bool next()
    {
        int i = 0;

        while( (i < m_step) && !end() )
        {
            m_current = m_current->next;
            i++;
        }

        return (i == m_step);
    }

    ~LinkList()
    {
        clear();
    }
};

}

#endif // LINKLIST_H

        

main.cpp 测试代码如下

#include <iostream>
#include "LinkList.h"

using namespace std;
using namespace DTLib;

int main()
{
    LinkList<int> list;

    for(int i=0; i<5; i++)
    {
        list.insert(i);
    }

    for(list.move(0); !list.end(); list.next())
    {
        cout << list.current() << endl;
    }

    return 0;
}

        我们编译运行看看结果

图片.png

        程序直接就爆掉了,那么我们下来来分析下这个结果。那么问题究竟在哪呢?问题就出在 SmartPointer 的设计方案上;其中有一条就是一片堆空间最多只能由一个指针标识,那么我们在上面的实现上,遍历的时候就需要多个指针。这时就需要创建一个新的智能指针了。结构如下

图片.png

        我们需要创建一个 Pointer 类,它是智能指针的抽象父类(模板)。有如下特性:

        1、纯虚析构函数 virtual `Pointer() = 0; 因为它是需要被继承的,所以为纯虚函数。

        2、重载 operator -> ()

        3、重载 opeartor * ()

        新的设计方案如下:


Pointer.h 源码

#ifndef POINTER_H#define POINTER_H

#include "Object.h"

namespace DTLib
{

template < typename T >
class Pointer : public Object
{
protected:
    T* m_pointer;
public:
    Pointer(T* p = NULL)
    {
        m_pointer = p;
    }

    T* operator -> ()
    {
        return m_pointer;
    }

    T& operator* ()
    {
        return *m_pointer;
    }

    bool isNull()
    {
        return (m_pointer == NULL);
    }

    T* get()
    {
        return m_pointer;
    }
};

}

#endif // POINTER_H

        我们在基于上面的 Pointer 类和原来实现的 SmartPointer 类来实现新的 SmartPointer 类。


SmartPointer.h 源码

#ifndef SMARTPOINTER_H
#define SMARTPOINTER_H

#include "Pointer.h"

namespace DTLib
{

template < typename T >
class SmartPointer : public Pointer<T>
{
public:
    SmartPointer(T* p = NULL) : Pointer<T>(p)
    {

    }

    SmartPointer(const SmartPointer<T>& obj)
    {
        this->m_pointer = obj.m_pointer;

        const_cast<SmartPointer<T>&>(obj).m_pointer = NULL;
    }

    SmartPointer<T>& operator= (const SmartPointer<T>& obj)
    {
        if( this != &obj )
        {
            T* p = this->m_pointer;

            this->m_pointer = obj.m_pointer;

            const_cast<SmartPointer<T>&>(obj).m_pointer = NULL;

            delete p;
        }

        return *this;
    }

    ~SmartPointer()
    {
        delete this->m_pointer;
    }
};

}

#endif // SMARTPOINTER_H

  

main.cpp 源码

#include <iostream>
#include "SmartPointer.h"

using namespace std;
using namespace DTLib;

class Test : public Object
{
public:
    Test()
    {
        cout << "Test()" << endl;
    }
    ~Test()
    {
        cout << "~Test()" << endl;
    }
};

int main()
{
    SmartPointer<Test> sp = new Test();
    SmartPointer<Test> spn;

    spn = sp;

    return 0;
}

        我们来看看结果有没有改变

图片.png

        智能指针的结果还是没有改变。那么我们来想下,如何在实现 SharedPointer 使得多个智能指针对象可以指向同一片堆内存,同时支持堆内存的自定释放呢?

        SharedPointer 的设计要点,首先它必须是类模板。通过计数机制(ref)对内存进行标识:如果堆内存被指向时,ref++;如果指针被置空时,ref--;当 ref == 0时,进行堆内存的释放。下来我们来看看计数机制的原理,如下

图片.png

        我们在创建一个对象时,便计数 ref++;销毁一个对象时,便 ref--。下来我们来看看 SharedPointer 类具体是怎么写的


SharedPointer.h 源码

#ifndef SHAREDPOINTER_H
#define SHAREDPOINTER_H

#include "Pointer.h"
#include "Exception.h"

namespace DTLib
{

template < typename T >
class SharedPointer : public Pointer<T>
{
private:
    int* m_ref;     // 计数机制成员指针

    void assign(const SharedPointer<T>& obj)
    {
        this->m_ref = obj.m_ref;
        this->m_pointer = obj.m_pointer;

        if( this->m_ref )
        {
            (*this->m_ref)++;
        }
    }
public:
    SharedPointer(T* p = NULL) : m_ref(NULL)
    {
        if( p )
        {
            this->m_ref = static_cast<int*>(std::malloc(sizeof(int)));

            if( this->m_ref )
            {
                *(this->m_ref) = 1;
                this->m_pointer = p;
            }
            else
            {
                THROW_EXCEPTION(NoEnoughMemoryException, "No memory to create SharedPointer object ...");
            }
        }
    }

    SharedPointer(const SharedPointer<T>& obj)
    {
        assign(obj);
    }

    SharedPointer<T>& operator= (const SharedPointer<T>& obj)
    {
        if( this != &obj )
        {
            clear();
            assign(obj);
        }

        return *this;
    }

    void clear()
    {
        T* toDel = this->m_pointer;
        int* ref = this->m_ref;

        this->m_pointer = NULL;
        this->m_ref = NULL;

        if( ref )
        {
            (*ref)--;

            if( *ref == 0 )
            {
                free(ref);

                delete toDel;
            }
        }
    }

    ~SharedPointer()
    {
        clear();
    }
};

}

#endif // SHAREDPOINTER_H

        我们来写点测试代码,看看 SharedPointer 类写的是否正确

#include <iostream>
#include "SharedPointer.h"

using namespace std;
using namespace DTLib;

class Test : public Object
{
public:
    int value;

    Test() : value(0)
    {
        cout << "Test()" << endl;
    }
    ~Test()
    {
        cout << "~Test()" << endl;
    }
};

int main()
{
    SharedPointer<Test> sp0 = new Test();
    SharedPointer<Test> sp1 = sp0;
    SharedPointer<Test> sp2 = NULL;

    sp2 = sp1;

    sp2->value = 100;

    cout << sp0->value << endl;
    cout << sp1->value << endl;
    cout << sp2->value << endl;

    cout << (sp2 == sp0) << endl;

    return 0;
}

        我们来看看结果

图片.png

        我们看到他们三个对象的值都是100,但是在 sp2 == sp0 的比较时,返回值却为 0。也就是说他们并不相等,那肯定是不行等了,因为我们还没有实现相等比较操作符,由于 SharedPointer 支持多个对象同时指向同一片堆空间;因此,必须支持比较操作。下来我们来继续实现下

template < typename T >
bool operator == (const SharedPointer<T>& l, const SharedPointer<T>& r)
{
    return (l.get() == r.get());
}

template < typename T >
bool operator != (const SharedPointer<T>& l, const SharedPointer<T>& r)
{
    return !(l == r);
}

        我们再来编译下代码

图片.png

        发现还是出错了,仔细看看错误。是因为它里面是 const 类型的对象,因此需要在父类 Pointer 里面将类型声明为 const 即可,改变后的 Pointer 如下所示


Pointer.h 源码

#ifndef POINTER_H
#define POINTER_H

#include "Object.h"

namespace DTLib
{

template < typename T >
class Pointer : public Object
{
protected:
    T* m_pointer;
public:
    Pointer(T* p = NULL)
    {
        m_pointer = p;
    }

    T* operator -> ()
    {
        return m_pointer;
    }

    const T& operator* () const
    {
        return *m_pointer;
    }

    const T* operator -> () const
    {
        return m_pointer;
    }

    T& operator* ()
    {
        return *m_pointer;
    }

    bool isNull() const
    {
        return (m_pointer == NULL);
    }

    T* get() const
    {
        return m_pointer;
    }
};

}

        上面的错误中还有一个警告,它说的是拷贝构造函数中应该初始化父类,因此在函数名后面加上 Pointer(NULL) 就OK,我们再来编译下试试看

图片.png

        我们看到已经正确输出了,下来我们试着在 sp2 == sp0 之前将 sp2 清空,看看结果是否为 0

图片.png

        我们看到结果已经是正确的了。接下来我们来试试 main 函数中的代码这样写呢?

const SharedPointer<Test> sp0 = new Test();    
sp0->value = 100;

        我们试试看编译能否通过

图片.png

        代码直接就报错了,因为我们要将一个 const 类型的对象进行赋值操作,肯定不行的。我们再来看看智能指针的使用军规:1、只能用来指向堆空间中的单个变量(对象);2、不同类型的智能指针对象不能混合使用;3、不要使用 delete 释放智能指针指向的堆空间。通过今天对智能指针的再次的学习,总结如下:1、SharedPointer 最大程度的模拟了原生指针的行为;2、计数机制确保多个智能指针合法的指向同一片堆空间;3、智能指针只能用于指向堆空间中的内存,不同类型的智能指针不要混合使用;4、堆对象的生命周期由智能指针进行管理。

猜你喜欢

转载自blog.51cto.com/12810168/2167150