智能指针的实现机理

本文详细介绍了智能指针的实现原理,包括构造函数、析构函数、重载操作符以及赋值函数的实现。通过一个测试类,展示了如何在代码中使用智能指针,实现了对象的自动管理。
  • 介绍

  智能指针是用来实现指针指向的对象的共享的。其实现的基本思想:
  1.每次创建类的新对象时,初始化指针并将引用计数置为1;
  2.当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;
  3.对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数减至0,则删除对象),并增加右操作数所指对象的引用计数;
  4.调用析构函数时,减少引用计数(如果引用计数减至0,则删除基础对象);
  4.重载“->”以及“*”操作符,使得智能指针有类似于普通指针的操作。
  根据以上分析,首先可以得出下面的类模板原型。

template <class T>
class SmartPointer
{
public:

    SmartPointer(T *p = 0);//构造函数
    SmartPointer(const SmartPointer& src);//拷贝构造函数
    SmartPointer& operator = (const SmartPointer& rhs);//赋值函数

    T* operator -> ();//重载->
    T& operator * ();//重载*
    ~SmartPointer();//析构函数

private:

    void MinusRef()
    {
        //被其他成员函数所调用
        if (--*m_pRef == 0)//自身的引用计数减1
        {
            //如果计数为0,则释放内存
            delete m_ptr;

            delete m_pRef;
        }

    }

    T *m_ptr;                           //保存对象指针
    size_t *m_pRef;                     //保存引用计数
};

  上面的私有成员函数MinusRef将引用计数减1。如果引用计数减至0,则删除m_ptr所指对象。根据前面的分析,MinusRef只被赋值函数以及析构函数使用。
下面说明各个成员的具体定义。
  首先是构造函数与析构函数的定义。普通构造函数中,m_ptr与p指向同一块内存,并初始化引用计数为1。拷贝构造函数中与普通构造函数的不同之处为引用计数需要加1。析构函数调用私有成员MinusRef对引用计数递减,并且判断是否需要释放对象。代码如下。

template<class T>
SmartPointer<T>::SmartPointer(T *p)     //普通构造函数
{
    m_ptr = p;                          //m_ptr与p指向同一内存
    m_pRef = new size_t(1);             //m_pRef初值为1
}

template<class T>
SmartPointer<T>::SmartPointer(const SmartPointer<T>& src)//拷贝构造函数
{
    m_ptr = src.m_ptr;      //m_ptr与src.m_ptr指向同一内存
    m_pRef++;
    m_pRef = src.m_pRef;    //拷贝引用计数

}

template<class T>
SmartPointer<T>::~SmartPointer()       //析构函数
{
    MinusRef();             //引用减1,如果减后的引用为0,则释放内存
    std::cout<<"SmartPointer: Destructor"<<std::endl;
}

  接下来是“->”和“*”的重载。这两个函数很简单,只需要分别返回m_ptr以及m_ptr所指的内容即可。注意,如果m_ptr此时为空,则应该抛出异常。代码如下。


template<class T>
T* SmartPointer<T>::operator -> ()  //重载 ->
{
    if (m_ptr)
        return m_ptr;

    //m_ptr为NULL时,抛出异常

    throw std::runtime_error("access through NULL pointer");
}
template<class T>
T& SmartPointer<T>::operator * () //重载 *
{
    if (m_ptr)
        return *m_ptr;
    //m_ptr为NULL时,抛出异常

    throw std::runtime_error("dereference of NULL pointer");
}

  最后是赋值函数的实现:

template<class T>

SmartPointer<T>& SmartPointer<T>::operator = (const SmartPointer<T>& rhs)//赋值函数
{
    ++*rhs.m_pRef;      //rhs的引用加1
    MinusRef();         //自身指向的原指针的引用减1
    m_ptr = rhs.m_ptr;  //m_ptr合rhs.m_ptr指向同一个对象
    m_pRef = rhs.m_pRef; //复制引用
    return *this;
}

  这样,就可以像 std::auto_ptr那样来使用SmartPointer。以下先定义一个测试类,测试程序如下。

class Test
{
public:
    Test() {name = NULL;}
    Test(const char* strname)//构造函数
    {
        name = new char[strlen(strname)+1];//分配内存
        strcpy(name, strname);//拷贝字符串
    }
    Test& operator = (const char *namestr)//赋值函数
    {
        if (name != NULL)
        {
            delete name;//释放原来的内存
        }
        name = new char[strlen(namestr)+1];//分配新内存
        strcpy(name, namestr);//拷贝字符串
        return *this;
    }
    void ShowName() {cout << name << endl;}
    ~Test()
    {
        if (name != NULL)
        {
            delete name;
        }
        name = NULL;
        cout << "delete name" << endl;
    }
public:
    char *name;
};

int _tmain(int argc, _TCHAR* argv[])
{
    SmartPointer<Test> t1;//空指针
    SmartPointer<Test> t2(new Test("fefd"));
    SmartPointer<Test> t3(new Test("wewew"));
    try
    {
        t1->ShowName();//空指针调用抛出异常
    }
    catch (const exception& err)
    {
        cout << err.what() << endl;
    }

    t2->ShowName();     //使用t2调用showName()
    *t2 = "David";      //使用t2给对象赋值
    t2->ShowName();     //使用t2调用showName()
    t2 = t3;            //赋值,原来t2的对象引用为0,发生析构
                        //而t3的对象引用加1
    cout << "End of main..." << endl;

    getchar();
    return 0;
}

  main函数代码第41行,t1指向一个NULL指针,因此调用ShowName时会出现异常(异常在重载的“->”函数中被抛出)。
  main函数代码第48~50行,使用SmartPtr对象对Test对象进行操作,其方法与使用Test对象指针的操作方法相同。
  main函数代码第51行,对t2进行赋值操作,操作完成后,t2引用的原对象发生析构(此对象没有SmartPtr对象引用了),t2和t3引用同一个对象,于是这个对象的引用计数加1。注意,这里我们并没有显示地对t2所引用的原对象进行释放操作,这就是智能指针的精髓所在。

文章已迁移到个人网站 –>《智能指针的实现机理》

### C++中动态内存分配的机理 在C++中,`malloc`和`new`是两种常见的动态内存分配方式,但它们在实现机制和使用场景上存在显著差异。 #### 1. `malloc` 的工作原理 `malloc` 是 C 语言中的标准库函数,用于从堆中分配指定大小的内存块。它返回一个指向分配内存的指针,类型为 `void*`,需要显式地转换为目标类型的指针[^1]。 - `malloc` 只负责分配内存,并不会对分配的内存进行初始化。 - 使用 `malloc` 分配的内存需要通过 `free` 显式释放,否则会导致内存泄漏。 - 示例代码如下: ```cpp int* ptr = (int*)malloc(sizeof(int)); // 分配一个 int 类型的内存 if (ptr != nullptr) { *ptr = 10; // 初始化内存内容 } free(ptr); // 释放内存 ``` #### 2. `new` 的工作原理 `new` 是 C++ 中的运算符,用于动态分配对象或数组的内存,并自动调用构造函数完成初始化[^1]。 - `new` 的底层实现依赖于 `operator new`,而 `operator new` 实际上可能调用了 `malloc` 来分配原始内存[^2]。 - 对于单个对象,`new` 不仅分配内存,还会调用对象的构造函数以完成初始化。 - 对于数组,`new[]` 会为每个元素调用构造函数。 - 示例代码如下: ```cpp class A { public: A() { std::cout << "Constructor called" << std::endl; } ~A() { std::cout << "Destructor called" << std::endl; } }; A* obj = new A(); // 分配内存并调用构造函数 delete obj; // 调用析构函数并释放内存 ``` #### 3. 动态内存分配的对比 | 特性 | `malloc` 和 `free` | `new` 和 `delete` | |-----------------|---------------------------------------|-----------------------------------------| | 内存分配 | 分配未初始化的原始内存 | 分配内存并调用构造函数 | | 构造函数调用 | 不支持 | 支持 | | 错误处理 | 返回 `nullptr` | 抛出异常(如 `std::bad_alloc`) | | 类型安全性 | 需要手动类型转换 | 自动处理类型转换 | #### 4. 定位 `new` 表达式 除了普通的 `new`,C++ 还提供了定位 `new` 表达式,用于在已经分配的内存中构造对象[^3]。这在某些特殊场景下非常有用,例如硬件设备地址映射或容器预分配内存。 ```cpp void* rawMemory = malloc(sizeof(A)); // 分配原始内存 A* obj = new (rawMemory) A(); // 在 rawMemory 中构造对象 obj->~A(); // 手动调用析构函数 free(rawMemory); // 释放原始内存 ``` #### 5. 堆与动态内存管理 动态内存通常位于堆中,堆的特点是向上增长,且其生命周期由程序员显式控制[^4]。动态内存分配的核心挑战在于避免内存泄漏和悬挂指针等问题,因此现代 C++ 提倡使用智能指针(如 `std::unique_ptr` 和 `std::shared_ptr`)来简化内存管理。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值