智能指针与引用计数详解(二)

智能指针与引用计数详解(一)当中讲了智能指针还有改进的地方,下面具体问题具体分析。
一、智能指针的赋值方法改进
上一章的赋值方法中只要是赋值都是右操作数引用计数加一,左操作数引用计数减一。没有考虑过引用计数对象自赋值的情况。
比如按照上一章代码,在main函数中做一下修改:

    int *ip = new int(12);
    HasPtr ptr(ip, 20);           
    HasPtr ptr1(ptr);
    HasPtr ptr2(ptr);
    {
        HasPtr ptr3 = ptr1;         //上一章是HasPtr ptr3 = NULL
        ptr3 = ptr;           
    }

上述代码“ptr3 = ptr; ”中左操作对象和右操作对象引用计数对象是一样的。

HasPtr& HasPtr::operator=(const HasPtr &rhs)
{
    cout<<"HasPtr assignment rhs uptr= "<<rhs.m_uptr<<endl;
    cout<<"HasPtr assignment this uptr = "<<this->m_uptr<<endl;
    ++rhs.m_uptr->m_useCount;
    if (--m_uptr->m_useCount == 0) {
        delete m_uptr;
        m_uptr = NULL;
    }
    m_uptr = rhs.m_uptr;
    m_val = rhs.m_val;
    return *this;
}

输出结果:
HasPtr assignment rhs uptr= 0x149ac40
HasPtr assignment this uptr = 0x149ac40

从输出结果可知两个智能指针对象中的引用计数对象是相等的,那么这时候赋值函数中的右操作数加一、左操作数减一其实相当于同一个对象的引用计数加一再减一了,这种情况作者分析认为没必要执行这一步多余的操作。
所以给出修改建议:

HasPtr& HasPtr::operator=(const HasPtr &rhs)
{
    cout<<"HasPtr assignment rhs uptr= "<<rhs.m_uptr<<endl;
    cout<<"HasPtr assignment this uptr = "<<this->m_uptr<<endl;
    int uPtrIsSmae = rhs.m_uptr == this->m_uptr;
    cout<<"HasPtr assignment uPtrIsSmae = "<<uPtrIsSmae<<endl;
    if (!uPtrIsSmae) {
        ++rhs.m_uptr->m_useCount;
        if (--m_uptr->m_useCount == 0) {
            delete m_uptr;
            m_uptr = NULL;
        }
        m_uptr = rhs.m_uptr;
        m_val = rhs.m_val;
    }
    return *this;
}

输出结果:
U_Ptr constructor
HasPtr constructor
HasPtr copy constructor m_uptr->m_useCount = 1
HasPtr copy constructor m_uptr->m_useCount = 2
HasPtr copy constructor m_uptr->m_useCount = 3
HasPtr assignment rhs uptr= 0x1ed4c40
HasPtr assignment this uptr = 0x1ed4c40
HasPtr assignment uPtrIsSmae = 1
HasPtr destruct m_uptr->m_useCount = 4
HasPtr destruct m_uptr->m_useCount = 3
HasPtr destruct m_uptr->m_useCount = 2
HasPtr destruct m_uptr->m_useCount = 1
U_Ptr destruct

二、智能指针传入参数扩展
之前的所有测试使用的都是int 指针,如果指针对象换了是不是得重写一个引用计数类呢?而且重写的类跟之前的设计是一模一样的,只是换一个指针对象而已,C++中有类模板技术,所有咱们可以将智能指针和引用计数类都写成模板类,这个就可以适配各种指针对象了。
直接上代码:
uPtr.h

#ifndef _UPtr_H
#define _UPtr_H
#include <iostream>
using namespace std;

template <typename T>
class HasPtr;

template <typename T>
class U_Ptr {
    private:
    friend class HasPtr<T>;

    U_Ptr(T *p);
    ~U_Ptr();
    
    T *m_ip;
    int m_useCount;
};

template <typename T> 
U_Ptr<T>::U_Ptr(T *p)
    :m_ip(p)
    ,m_useCount(1)
{

}

template <typename T> 
U_Ptr<T>::~U_Ptr()
{
    cout<<"U_Ptr destruct"<<endl;
    if (NULL != m_ip) {
    	delete m_ip;
    	m_ip = NULL;
    }
}
#endif

hasPtr.h

#ifndef _HasPtr_H
#define _HasPtr_H
#include "uPtr.h"
#include <iostream>
using namespace std;
template <class T>
class HasPtr {

    public:
        HasPtr(T *p, int i);
        HasPtr(const HasPtr &ptr);
        HasPtr& operator=(const HasPtr &rhs);
        ~HasPtr();
        T *get_ptr() const;
        void set_ptr(T *p);

        int get_int() const;
        void set_int(int i);

        T get_ptr_val() const;
        void set_ptr_val(T val);

    private:
        U_Ptr<T> *m_uptr;
        int m_val;

};
template <typename T>
HasPtr<T>::HasPtr(T *p, int i)
    :m_uptr(new U_Ptr<T>(p))
    ,m_val(i)
{
    cout<<"HasPtr constructor"<<endl;
}

template <typename T>
HasPtr<T>::HasPtr(const HasPtr &ptr)
    :m_uptr(ptr.m_uptr)
    ,m_val(ptr.m_val)
{
    ++m_uptr->m_useCount;
    cout<<"HasPtr copy constructor m_uptr->m_useCount = "<<m_uptr->m_useCount<<endl;
}

template <typename T>
HasPtr<T>& HasPtr<T>::operator=(const HasPtr &rhs)
{
    bool uptrIsSame = this->m_uptr == rhs.m_uptr;
    cout<<"HasPtr assignment rhs.m_uptr->m_useCount = "<<rhs.m_uptr->m_useCount<<endl;
    cout<<"HasPtr assignment m_uptr->m_useCount = "<<m_uptr->m_useCount<<endl;
    if (!uptrIsSame) {
        ++rhs.m_uptr->m_useCount;
        if (--m_uptr->m_useCount == 0) {
            delete m_uptr;
            m_uptr = NULL;
        }
        m_uptr = rhs.m_uptr;
        m_val = rhs.m_val;
    }
    return *this;
}

template <typename T>
T *HasPtr<T>::get_ptr() const {
    return m_uptr->m_ip;
}

template <typename T>
void HasPtr<T>::set_ptr(T *p) {
    m_uptr->m_ip = p;
}

template <typename T>
int HasPtr<T>::get_int() const {
    return m_val;
}

template <typename T>
void HasPtr<T>::set_int(int i) {
    m_val = i;
}

template <typename T>
T HasPtr<T>::get_ptr_val() const {
    return *m_uptr->m_ip;
}

template <typename T>
void HasPtr<T>::set_ptr_val(T val) {
    *m_uptr->m_ip = val;
}

template <typename T>
HasPtr<T>::~HasPtr() {
    cout<<"HasPtr destruct m_uptr->m_useCount = "<<m_uptr->m_useCount<<endl;
    if (--m_uptr->m_useCount == 0) {
        delete m_uptr;
        m_uptr = NULL;
    }
}
#endif

main函数

    int *p = new int(12);
    HasPtr<int> ptr(p, 20);
    HasPtr<int> ptr1(ptr);
    HasPtr<int> ptr2(ptr1);
    {
        HasPtr<int> ptr3 = ptr1;
        ptr3 = ptr;
    }

输出结果是一样的。

PS:这里边遇到一个坑,由于模板计数不熟悉,所以之前按照正常套路,在uPtr.h和hasPtr.cpp文件中定义对应.h文件中的函数,结果编译报错:

main.cpp:(.text+0x38): undefined reference to `HasPtr<int>::HasPtr(int*, int)'
main.cpp:(.text+0x4b): undefined reference to `HasPtr<int>::HasPtr(HasPtr<int> const&)'
main.cpp:(.text+0x5e): undefined reference to `HasPtr<int>::HasPtr(HasPtr<int> const&)'
main.cpp:(.text+0x71): undefined reference to `HasPtr<int>::HasPtr(HasPtr<int> const&)'
main.cpp:(.text+0x84): undefined reference to `HasPtr<int>::operator=(HasPtr<int> const&)'
main.cpp:(.text+0x90): undefined reference to `HasPtr<int>::~HasPtr()'
main.cpp:(.text+0xa1): undefined reference to `HasPtr<int>::~HasPtr()'
main.cpp:(.text+0xad): undefined reference to `HasPtr<int>::~HasPtr()'
main.cpp:(.text+0xb9): undefined reference to `HasPtr<int>::~HasPtr()'
main.cpp:(.text+0xd1): undefined reference to `HasPtr<int>::~HasPtr()

后面查询资料发现模板类的所有定义都必须放在头文件中。所以修改之后就可以了。
参考资料:
https://stackoverflow.com/questions/7731021/c-gcc-undefined-reference-to-stackintstackint

当然也有另外一种解决方案,就是编译的时候将对应的.h和.cpp都放在main.cpp中:

类似这样:
#include "hasPtr.h"
#include "hasPtr.cpp"

这种方法不推荐
参考资料:
https://stackoverflow.com/questions/13216844/undefined-reference-to-linkedlistintpush-frontint

扩展:
项目开发中,通常会用到继承,有继承就意味着很多地方会使用多态。比如现在包含的指针是一个有基类的指针对象,那么如果要用到该基类的另一个派生类难道要再重新new一个对象使用吗?具体方案请见智能指针与句柄详解

发布了88 篇原创文章 · 获赞 17 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/yj_android_develop/article/details/84962414