一个通用的 C++ 模板类,用于实现智能指针
编写一个通用的 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 联系我。