智能指针原理分析与自己的shared_ptr实现

原理分析

内存管理历来是C++编程的一项需要小心费力气的活,因为C++本身不带GC机制,所有的内存管理都需要我们手动实现,从malloc / freenew / delete,再到allocator的出现,无非都是为了更合理简单的避免内存泄露。

指针本身是一个用法十分灵活并且功能强大的工具,然而它对内存的直接掌控也使得它不得不常常背起内存泄露的黑锅,因为忘记删除指针或者将一个指针删除两次的错误往往十分隐蔽,难以侦查。为了解决这个问题,boost中的智能指针终于还是在C++11中被纳入了标准库。

来看个例子:


std::string* stringPtr1 = new std::string;
std::string* stringPtr2 = new std::string[100];
… // do something
delete stringptr1; // 删除一个对象
delete[] stringPtr2; // 删除一个由对象组成的数组


看上去一切都很好:new出来的两个对象在完成任务后都被delete掉了。然而考虑在do something的时候发生点什么,比如抛出异常,程序跳转,或者函数返回了,那么会发生什么呢?显然,这两个delete并不会得到执行,也就是说,内存泄露了。假如把上面的指针定义成智能指针,那么一切问题都将烟消云散,如下。


shared_ptr stringPtr1(new std::string);
shared_ptr stringPtr2 = new std::string[100];
… // do something


那么智能指针是如何帮助我们管理内存的呢?事实上,智能指针就是一个作用是资源管理的类,它是你在堆栈上声明的类模板,并可通过使用指向某个堆分配的对象的原始指针进行初始化(RAII)。

在初始化智能指针后,它将拥有原始指针,这意味着智能指针负责删除原始指针指定的内存, 智能指针析构函数包括要删除的调用,当智能指针超出范围时将调用其析构函数,析构函数会自动释放资源。

现代C++中的三种智能指针简单介绍如下(引自MSDN智能指针(现代 C++)):

  • unique_ptr
    只允许基础指针的一个所有者。 除非你确信需要 shared_ptr,否则请将该指针用作 POCO 的默认选项。 可以移到新所有者,但不会复制或共享。 替换已弃用的 auto_ptr。 与 boost::scoped_ptr 比较。 unique_ptr 小巧高效,大小等同于一个指针且支持 rvalue 引用,从而可实现快速插入和对 STL 集合的检索。
  • shared_ptr
    采用引用计数的智能指针。 如果你想要将一个原始指针分配给多个所有者(例如,从容器返回了指针副本又想保留原始指针时),请使用该指针。 直至所有 shared_ptr 所有者超出了范围或放弃所有权,才会删除原始指针。 大小为两个指针:一个用于对象,另一个用于包含引用计数的共享控制块。
  • weak_ptr
    结合 shared_ptr 使用的特例智能指针。 weak_ptr 提供对一个或多个 shared_ptr 实例拥有的对象的访问,但不参与引用计数。 如果你想要观察某个对象但不需要其保持活动状态,请使用该实例。 在某些情况下,用于断开 shared_ptr 实例间的循环引用。

shared_ptr 解析

shared_ptr的原理是引用计数法reference counting,每多一个智能指针指向同一个对象时,引用+1,而析构则相反,如果计数为零,则保存的指针被删除。

正常的指针的功能智能指针都有,如(以下用mySmartPtr代替shared_ptr,这是自己实现的智能指针名字)


int *pi = new int(42);     
mySmartPtr<int> *hpa(new mySmartPtr<int>(pi));    // 构造函数  
mySmartPtr<int> *hpb = new mySmartPtr<int>(*hpa); // 拷贝构造函数  
mySmartPtr<int>  hpd = *hpa;                      // 拷贝构造函数  

同时,无论复制shared_ptr多少次,只要不出现循环引用,总是可以析构掉的,如


vector<mySmartPtr<Base> > obj;        
vector<mySmartPtr<Base> > obj2;  
obj2.push_back(obj[0]);

除了析构对象的工作,shared_ptr同时也需要帮助我们解决类型转换的问题。一个是多态性的体现,即以基类指针指向派生类对象:


vector<mySmartPtr<Base> > obj;        // 父类指针vector
obj.push_back(new Derived1);          // 指向子类1
obj.push_back(new Derived2);          // 指向子类2

另一个是dynamic_cast转型,如下,使用dynamic_cast<Derived2*>(static_cast<Base*>(new Derived1))会得到空指针:


mySmartPtr<Derived1> d1 = new Derived1;
mySmartPtr<Base> b = d1;              // b指向的是Derived1
mySmartPtr<Derived2> d2 = b.Cast<Derived2>();  // 转型失败返回空指针

具体来说,一个智能指针的实现要完成以下功能:

  1. 没有参数构造的时候,初始化为空,即对象和引用计数的两个指针都为0

  2. 使用指针为参数构造时,拥有此指针,在没有智能指针指向它时进行析构

  3. 智能指针复制时,两个智能指针共同拥有内部指针,引用计数同时+1

  4. 智能指针可以使用智能指针或普通指针重新赋值。重载=操作符,对于智能指针赋值,需要考虑是否自赋值,以避免将自身析构了后再重新赋值,而普通指针赋值给智能指针,则不需要考虑自赋值,因为两者本身是两个类型

  5. 获得底层指针的访问,定义getPtrPointer()getPtrCounter()来分别返回底层指针和引用计数,定义operator bool()来处理智能指针隐式转换为bool的情况

  6. 重载->×操作符 ,来实现与普通指针相同的指针访问

  7. 需要支持隐式指针类型转换,static_cast不支持而dynamic_cast支持的转换则使用Cast<T2>()成员函数来解决。考虑定义友元类,以防止指向派生类的智能指针有权限访问基类的内部对象;当转型不成功时,返回为空

  8. 如果一个裸指针直接用来创建两个智能指针的话,期望的情况是当两个智能指针析构掉的时候,该指针会被delete两次从而崩溃(这是shared_ptr的特点)

  9. 不处理循环引用(也是shared_ptr的特点),可以通过与weak_ptr协作来打破循环

  10. 暂时不实现deleter机制,即只能传递给mySmartPtr一个参数

实际上,第8/第9两点往往是使用智能指针出现问题最多的地方,平时使用过程中要多加留意。

一个简单的实现

测试用例


/**********************************************
    > File Name: testmySmartPtr.cpp
    > Author: ye_create
    > Mail: 
    > Created Time: 2015年05月28日 星期四 13时03分42秒
 ***********************************************/
#include <iostream>
#include <vector>
#include "mySmartPtr.h"
using namespace std;

class Base             // 定义一个基类
{
public:
    virtual ~Base(){
        cout << "class Base" << endl;
    }
};

class Derived1 : public Base   // 派生类1
{
public:
    ~Derived1(){
        cout << "class Derived1" << endl;
    }
};    

class Derived2 : public Base    // 派生类2
{
public:
    ~Derived2(){
        cout << "class Derived2" << endl;
    }
};

int main()
{   
    int *pi = new int(42);     
    mySmartPtr<int> *hpa(new mySmartPtr<int>(pi));        // 构造函数  
    mySmartPtr<int> *hpb = new mySmartPtr<int>(*hpa);     // 拷贝构造函数  
    mySmartPtr<int>  hpd = *hpa;                          // 拷贝构造函数  
    // 观察引用计数的变化
    cout << hpa->getPtrCounter() << " " << hpb->getPtrCounter() << " "<< hpd.getPtrCounter() << endl;  
    delete hpa;  
    cout << hpd.getPtrCounter() << endl;  
    delete hpb;  
    cout << hpd.getPtrCounter() << endl;

    // 观察派生类向基类的隐式转换
    vector<mySmartPtr<Base> > obj;        // 父类指针vector
    obj.push_back(new Derived1);          // 存入子类
    obj.push_back(new Derived2);

    vector<mySmartPtr<Base> > obj2;  
    obj2.push_back(obj[0]);

    if (obj2[0])
        cout << "Cast Derived1 to Base successd" << endl;
    else
        cout << "Cast Derived1 to Base failed" << endl;

    // 观察不同类型的显式转换
    mySmartPtr<Derived1> d1 = new Derived1;
    mySmartPtr<Base> b = d1;              
    mySmartPtr<Derived2> d2 = b.Cast<Derived2>();  
    // d2是空,因为b指向的是Derived1而不是Derived2
    if (d2)
        cout << "Cast Derived1 to Derived2 successd" << endl;
    else
        cout << "Cast Derived1 to Derived2 failed" << endl;

    return 0;
}

mySmartPtr实现


/*************************************************************************
    > File Name: mySmartPtr.cpp
    > Author: ye_create
    > Mail: 
    > Created Time: 2015年05月28日 星期四 13时35分00秒
 ************************************************************************/

template <typename T>
class mySmartPtr
{
public:
    // 构造函数 默认为空
    mySmartPtr(): pointer(0), counter(0)
    {
    } 

    // 形参为指针的构造函数
    mySmartPtr(T* p): pointer(0), counter(0){
        *this = p;
    }

    // 复制构造函数
    mySmartPtr(const mySmartPtr<T> &p): 
    pointer(p.pointer), counter(p.counter){
        Increase();
    }

    ~mySmartPtr(){
        Decrease();
    }

    // 指针的赋值操作符,类型不同,不是自赋值
    mySmartPtr operator=(T* p){
        Decrease();
        if (p){
            pointer = p;
            counter = new int(1);
        }
        else {
            pointer = 0;
            counter = 0;
        }
        return *this;
    }

    // 智能指针赋值操作符
    mySmartPtr operator=(mySmartPtr<T> &p){
        // 处理自赋值
        if (this != &p){
            Decrease();
            pointer = p.pointer;
            counter = p.counter;
            Increase();
        }
        return *this;
    }

    operator bool() const{
        return counter != 0;
    }

    // ×操作符重载
    T* operator*() const{
        return this;
    }

    // ->操作符重载
    T* operator->() const{
        return pointer;
    }

    // 返回底层指针
    int getPtrPointer() const{
        return *pointer;
    }

    // 返回引用计数
    int getPtrCounter() const{
        return *counter;
    }

    // 处理父类子类的情况, ptr<derived>不能访问 ptr<based>的内部对象
    template<typename C> friend class mySmartPtr;

    template<typename C> 
    mySmartPtr(const mySmartPtr<C> &p): 
        pointer(p.pointer), counter(p.counter){
            Increase();
        }

    template<typename C>
    mySmartPtr<T> & operator=(const mySmartPtr<C> &p){
        Decrease();
        pointer = p.pointer;
        counter = p.counter;
        Increase();
        return *this;
    }

    // 处理内部使用 dynamic_cast做判断的转换,失败则空指针
    template<typename C>
    mySmartPtr<C> Cast() const{
        C* converted = dynamic_cast<C*>(pointer);
        mySmartPtr<C> result;
        if (converted){
            result.pointer = converted;
            result.counter = counter;
            result.Increase();
        }
        return result;
    }

private:
    T*      pointer;
    int*    counter;

    void Increase(){
        if (counter) 
            ++*counter;
    }

    void Decrease(){
        if (counter && --*counter == 0){
            delete pointer;
            delete counter;
            counter = 0;
            pointer = 0;
        }
    }

};

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值