65.9K
CodeProject 正在变化。 阅读更多。
Home

一个通用的 C++ 模板类,用于实现智能指针

starIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIconemptyStarIcon

1.40/5 (4投票s)

2002年10月6日

CPOL

5分钟阅读

viewsIcon

89545

编写一个通用的 C++ 模板类来实现智能指针,用于自动删除内存。

引言

智能指针在 C++ 世界中被广泛使用,以避免内存中留下悬空指针。智能指针通常有两个主要目标:一是使编程更安全,二是使编程更容易。它们是类似于普通指针的对象,但提供了更强大的内存管理功能。通常在 C++ 中,智能指针是以模板形式提供的,我们可以直接从模板构造一个引用我们类的智能指针。即使在复杂的 COM 程序中,智能指针也被广泛用于避免容易出现内存泄漏的代码。重载 `->` 运算符对于创建智能指针很有用。这使得对象像指针一样工作,并且在通过它们访问对象时执行一些额外操作。同样,`*` 运算符也可以被重载,以便在任何给定时刻返回指针。STL 还提供了 `auto_ptr` 模板,可以实现平滑的内存处理。除了重载 `=`、`*` 和 `->` 运算符外,它还提供了获取和释放引用的函数。同样,Microsoft ATL 和 COM 也提供了智能指针的构造。智能指针支持以下三个主要方面,有助于提高客户端 COM 程序的质量:

  • 构造和析构 - 智能接口指针的构造可以很简单,只需初始化指针为 null,也可以很复杂,比如调用 `CoCreateInstance` 来检索对所请求接口的指针。通常,智能指针类的构造函数会被重载以支持所有这些不同的功能。智能接口指针的析构函数可以自动调用 `Release`。
  • 复制和赋值 - 智能指针可以重载赋值运算符(=),因此任何复制指针的尝试都会自动调用 `AddRef`,从而减轻程序员的引用计数负担。
  • 解引用 - 最后,智能指针类可以重载间接运算符(*)来执行一些基本的错误检查,确保内部的“普通”指针不是 null 或以其他方式无效。

为什么要编写自己的智能指针?

在 C++ 中处理内存通常是一项痛苦的任务。与 Java 不同,C++ 没有垃圾回收的概念,可以自动释放其引用为 `NULL` 后的内存。在 C++ 中,内存的释放责任在于分配者,否则程序会消耗堆。这通常发生在程序员使用 `new` 分配内存但使用完毕后没有跟着调用 `delete` 的情况下。处理这种情况的一种方法是编写一个通用模板类,该类将自动删除分配的内存。最重要的是,每个人都会对编写自己的智能指针感兴趣。这有助于更好地理解智能指针的概念及其内部工作原理。既然有一种方法可以提交自己的智能指针,为什么不尝试一下,看看它实际上是如何工作的呢?在实现智能指针模板之前,让我们考虑下面的示例,该示例将解释内存泄漏通常是如何发生的。

//A simple class that will allocate memory to a string and 
//display it using a function
#include<iostream.h>
#include<string.h>
class MyClass
{
public:
    //ctor
    MyClass()
    {
        str=new char[255];
    }
    //dtor
    ~MyClass()
    {
        delete[] str;
    }
    //Function to display the string
    void display()
    {
        strcpy(str,"This is a test string");
        cout<<str<<endl;
    }
private:
    char* str;
};

现在,如果您有一个主程序,它实例化了这个类并分配了内存,然后调用了 display 函数。请看下面的代码:

//Main function
void main()
{
    MyClass* pMyClass=new MyClass();
    pMyClass->display();
}

在上面的程序中,您可以看到在调用 `new` 分配内存后没有调用 `delete`。这肯定会导致内存泄漏。由于这是一个小程序,很容易跟踪和删除内存。但在实际情况中,跟踪变得有点困难,因此始终建议编写一个模板,一旦使用完毕,它将帮助您自动释放内存。下面是一个模板类实现,可以帮助您释放内存:

#include<iostream.h>
#include<string.h>

//The template class definition for smart pointer
template<class T>
class AUTO_PTR 
{
public:
    typedef T element_type;
    //ctor
    explicit AUTO_PTR(T *pVal = 0) throw()
    {
        if(pVal)
            m_AutoPtr = pVal;
        else
            m_AutoPtr = NULL;
    }
    //copy ctor
    AUTO_PTR(const AUTO_PTR<T>& ptrCopy) throw()
    {
        if(ptrCopy)
            m_AutoPtr = ptrCopy;
        else
            m_AutoPtr = 0;
    }
    //overloading = operator
    AUTO_PTR<T>& operator=(AUTO_PTR<T>& ptrCopy) throw()
    {
        if(ptrCopy)
            m_AutoPtr = &ptrCopy;
        else
            m_AutoPtr = 0;
        return m_AutoPtr;
    }
    //dtor
    ~AUTO_PTR()
    {
        if(m_AutoPtr)
        {
            delete m_AutoPtr;
        }
    }
    //overloading * operator
    T& operator*() const throw()
    {
        return *m_AutoPtr;
    }
    //overloading -> operator
    T *operator->() const throw()
    {
        return m_AutoPtr;
    }
    //function to get the pointer to the class
    T *get() const throw()
    {
        return m_AutoPtr;
    }
private:
    T *m_AutoPtr;
};

class MyClass
{
public:
    //ctor
    MyClass()
    {
        str=new char[255];
        cout<<"Memory allocated"<<endl;
    }
    //dtor
    ~MyClass()
    {
        delete[] str;
        cout<<"Memory deallocated"<<endl;
    }
    //Function to display the string
    void display()
    {
        strcpy(str,"This is a test string");
        cout<<str<<endl;
    }
private:
    char* str;
};

现在,需要修改 `main()` 函数,使其使用 `AUTO_PTR` 模板类来分配和释放内存。修改后的 `main()` 函数如下所示:

void main()
{
    //Allocate memory to the class MyClass using the template
    AUTO_PTR<MyClass> b=AUTO_PTR<MyClass>(new MyClass);
    //Get the pointer and call the display function
    b.get()->display();
}

上面的程序是如何工作的?

看看 `main` 函数。在那里,使用 `AUTO_PTR` 模板创建了一个 `MyClass` 类型的指针。首先,它进入 `MyClass` 构造函数并根据需要分配内存。然后它进入 `AUTO_PTR` 构造函数,创建一个 `MyClass` 类型的智能指针。该指针用于 `MyClass` 引用的整个生命周期。调用 `get` 函数来获取 `MyClass` 类的引用,然后调用 `display` 函数来显示由该类分配的字符串。一旦使用完引用,它会自动释放分配的内存。如您所知,析构函数是按相反顺序调用的,因此这是按相反顺序发生的。尽管程序从未显式调用 `delete`,但由于智能指针的工作方式是释放内存,因此它首先进入 `AUTO_PTR` 的析构函数来删除 `m_AutoPtr` 指针,然后进入 `MyClass` 类的析构函数来释放分配给字符串变量的内存。因此,在程序结束时,内存被释放了。

上面的例子非常简单。它只是为了展示小的错误如何导致内存泄漏。这可以扩展到在复杂场景中使用该模板。将 `MyClass` 类视为演示模板用法的示例。您可以编写更复杂的类并使用该模板来分配和释放内存。玩得开心,尝试更复杂的类。如有任何疑问,请通过 bkumarp@yahoo.com 联系我。

© . All rights reserved.