我们在之前已经介绍过智能指针了,为什么我们还在这块再提出来呢?在之前的单链表(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; }
我们编译运行看看结果
程序直接就爆掉了,那么我们下来来分析下这个结果。那么问题究竟在哪呢?问题就出在 SmartPointer 的设计方案上;其中有一条就是一片堆空间最多只能由一个指针标识,那么我们在上面的实现上,遍历的时候就需要多个指针。这时就需要创建一个新的智能指针了。结构如下
我们需要创建一个 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; }
我们来看看结果有没有改变
智能指针的结果还是没有改变。那么我们来想下,如何在实现 SharedPointer 使得多个智能指针对象可以指向同一片堆内存,同时支持堆内存的自定释放呢?
SharedPointer 的设计要点,首先它必须是类模板。通过计数机制(ref)对内存进行标识:如果堆内存被指向时,ref++;如果指针被置空时,ref--;当 ref == 0时,进行堆内存的释放。下来我们来看看计数机制的原理,如下
我们在创建一个对象时,便计数 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; }
我们来看看结果
我们看到他们三个对象的值都是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); }
我们再来编译下代码
发现还是出错了,仔细看看错误。是因为它里面是 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<T>(NULL) 就OK,我们再来编译下试试看
我们看到已经正确输出了,下来我们试着在 sp2 == sp0 之前将 sp2 清空,看看结果是否为 0
我们看到结果已经是正确的了。接下来我们来试试 main 函数中的代码这样写呢?
const SharedPointer<Test> sp0 = new Test(); sp0->value = 100;
我们试试看编译能否通过
代码直接就报错了,因为我们要将一个 const 类型的对象进行赋值操作,肯定不行的。我们再来看看智能指针的使用军规:1、只能用来指向堆空间中的单个变量(对象);2、不同类型的智能指针对象不能混合使用;3、不要使用 delete 释放智能指针指向的堆空间。通过今天对智能指针的再次的学习,总结如下:1、SharedPointer 最大程度的模拟了原生指针的行为;2、计数机制确保多个智能指针合法的指向同一片堆空间;3、智能指针只能用于指向堆空间中的内存,不同类型的智能指针不要混合使用;4、堆对象的生命周期由智能指针进行管理。