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

使用智能指针在C++中自动进行垃圾回收

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.33/5 (24投票s)

2003 年 5 月 19 日

5分钟阅读

viewsIcon

115042

downloadIcon

1257

本文将介绍如何通过在类中集成垃圾回收器来防止程序发生内存泄漏。

引言

C++ 是目前最流行的编程语言。尽管许多人已转向 VB 和 Java 等其他高级语言,但它仍然是系统编程以及对性能要求极高的场合的首选语言。

C++ 提供了强大的动态内存分配和释放功能,但几乎没有 C++ 程序员没有被 C++ 程序的内存泄漏困扰过。原因是,在 C++ 中,您必须自己释放内存,而这有时会引发错误。

Java 和 C# 等一些高级语言提供了自动内存释放功能。这些语言内置了垃圾回收器,它会查找程序中不再有引用的内存并释放该内存。因此,程序员无需担心内存泄漏。在 C++ 程序中也拥有垃圾回收功能怎么样?

嗯,我们可以使用智能指针在程序中实现简单的垃圾回收功能,但这会付出一定的代价。对于您为其实现垃圾回收的类的每个实例,会增加一个额外的整数和一个字符变量的开销。本文提供了所有这些垃圾回收类的源代码。

什么是智能指针?

我说过我们可以使用“智能”指针来实现垃圾回收。但“智能”指针到底是什么?

C++ 允许您创建“智能指针”类来封装指针,并重载指针运算符以向指针操作添加新功能。C++ 的模板功能允许您创建通用的包装器来封装几乎任何类型的指针。

一个使用模板的智能指针的例子是封装 C++ 中二维数组的类。

template< class T> class ArrayT 
{
  T** m_ptr; // for double dimension
  int sx,sy; // size of dimensions of array
  public: 
  ArrayT( int dim1, int dim2) // in case of ArrayT<float> b(10,20) 
  {
    sx=dim1;sy=dim2; 
    m_ptr= new T*[dim1]; 
    for( int i=0;i<dim1;i++) 
      m_ptr[i]=new T[dim2];
  }
  T* operator[] ( < pan class=cpp-keyword>int index) 
      { return m_ptr[index]; } // to add [] operator to ArrayT 

  ~ArrayT() 
  {
    for( int d=0;d<sx;d++) 
      delete [] m_ptr[d]; 
    delete m_ptr; 
  } 
};

此类封装了 C++ 中任何对象的二维数组功能。您可以以这种方式扩展二维数组的功能。使用相同的技术,您还可以实现类似 STL 的可调整大小的集合类,如 vector。

现在,回到使用智能指针进行垃圾回收,我们如何在类中使用智能指针进行垃圾回收?

我们的垃圾回收器做什么?

我们将垃圾回收功能嵌入到特定的类中。这个简单的垃圾回收器在对象不再被引用时释放内存,从而防止内存泄漏。这真的很容易实现。

我们使用引用计数机制。每当对一个对象产生新的引用时,我们就增加引用计数,当它不再被引用时,即引用计数为 0 时,我们就释放内存。

实现

模板类 gcPtr<T> 实现了一个垃圾回收指针,指向任何派生自 RefCount 类的类。

template <class T>class RefCount 
{
  protected:
  
  int refVal; 
  T* p;

  public:
  
  RefCount(){ refVal=0; p=(T*)this;}
  void AddRef() { refVal++; }
  void ReleaseRef() 
  { 
    refVal--;
    if(refVal == 0) 
      delete [] this; 
  } 

  T* operator->(void) { return p; } 
  // Provide -> operator for class inheriting 

  // RefCount 
  // Similarly you can add other overloaded 
  // operators in this class //so that
  // you dont have to implement them again
  // and again. //Once you have added
  // these operators, they will be available
  // to all classes // inheriting from RefCount
  // to incorporate Garbage Collection. 
};

此类实现简单的引用计数。任何希望实现垃圾回收的类都应从此类派生。请注意,RefCount 接受一个模板参数。此参数用于重载派生自 RefCount 类以实现垃圾回收的类的 -> 运算符。即:

    class foo : public RefCount<foo><FOO>
    {
    };

gcPtr 是一个使用智能指针的模板类,它封装了所有垃圾回收过程。

template <class T> class gcPtr 
{
  T* ptr; 
  char c;
  
  public: 
  
  gcPtr() 
  { 
    c='0'; // called when variable declare // as gcPtr<foo> a; 
  }
  gcPtr(T* ptrIn) 
  { 
    ptr=ptrIn; 
    // called when variable declared
    // as gcPtr<foo> a=new foo; 
    ptr->AddRef(); 
    c='1'; 
  } 
  // assuming we have variable gcPtr<foo> x
  operator T*(void) { return ptr; } //for x[] 
  T& operator*(void) { return *ptr; } // for *x type operations 
  T* operator->(void){ return ptr; } // for x-> type operations
  gcPtr& operator=(gcPtr<T> &pIn) 
  // for x=y where y is also gcPtr<foo> object 
  {
    return operator=((T *) pIn);
  }
  gcPtr& operator=(T* pIn) 
  { 
    if(c=='1') 
    // called by gcPtr& operator=(gcPtr<T>&pIn) in case of 
    { // assignment 
      // Decrease refcount for left hand side operand 
      ptr->ReleaseRef();
      // of ‘=’ operator 
      ptr = pIn; 
      pIn->AddRef(); // Increase reference count for the Right Hand 
      // operand of ‘=’ operator 
      return *this; 
    }
    else 
    // if c=0 i.e variable was not allocated memory when // it was declared 
    { // like gcPtr<foo> x. in this case we //allocate memory using new 
      // operator, this will be called 
      ptr=pIn; 
      ptr->AddRef(); 
      return *this 
    }
  }
  ~gcPtr(void) { ptr->ReleaseRef(); } // Decrement the refcount 
};

现在,让我们看看 gcPt 类模板中发生了什么。它有两个构造函数。gcPtr() 用于在您仅声明变量但未使用 new 运算符为其分配内存的情况。gcPtr(T*) 用于在使用 new 运算符声明时将内存分配给 gcPtr 变量的情况。我们重载了 T* 运算符以支持数组表示法 []。它返回我们正在封装的类所管理的 T* 类型指针。-> 运算符也被重载以支持指针操作,如 x->func()。此运算符也返回 T* 指针。在赋值(如 x=y)的情况下,会发生一些有趣的事情。调用了 gcPtr& operator=(gcPtr<T> &pIn) 函数,该函数又调用 gcPtr& operator=(T* pIn) 函数,并且只执行此函数中的“if”块。请检查此代码

gcPtr& operator=(gcPtr<T> &pIn) // for x=y where y is also gcPtr<foo> object 
{
  return operator=((T *) pIn);
}

我们将输入的 gcPtr参数类型转换为 T*。如果赋值是 x=y(其中 xygcPtr<FOO> 的变量),这意味着我们将 y 类型转换为 foo* 并调用 gcPtr& operator=((foo*) y) 函数。现在,让我们看看此函数中是如何实现垃圾回收机制的。检查此“if”块的代码

  ptr->ReleaseRef();
  // Decrement the reference count for ‘x’. Now if the
  //reference count is zero, memory for x will
  // be de-allocated

  ptr = pIn; // assign (foo*) y to ptr which is
         //of type foo

  pIn->AddRef(); // Since we have made a new reference
                   //to (foo*) y, increment
                  //Reference count for (foo*)y

  return *this; // return the x variable by reference.vv

因此,这解释了在不再引用对象时如何进行垃圾回收。

如何在您的项目中实现垃圾回收?

请按照以下步骤在您的程序中实现此简单的垃圾回收机制。它非常简单。

  1. RefCountgcPtr 类添加到您的项目中。
  2. 让您的类从 RefCount 派生,并将您的类名作为模板参数传递给 RefCount

    例如

       class foo: public RefCount<FOO><FOO>
       {
          public:
          void func() { AfxMessageBox(“Hello World”) };
       };
  3. 现在,要声明您的类的指针,请使用 gcPtr <T> 类,并将您的类名作为模板参数传递。gcPtr<T> 是一个“智能”指针。在下面所示的方法之一中使用 new 运算符分配内存后,您可以使用 gcPtr就像您的类指针一样。它支持所有指针操作。您无需使用 delete 运算符释放内存。它将由 gcPtr 类处理。

    //First Method
    
    gcPtr<FOO> obj; // simple declaration
    obj=new foo; // Assign memory to obj
    obj->func();
    //Second Method
    gcPtr<FOO> obj=new obj;
    obj->func();
  4. 您还可以使用 gcPtr<T> 类声明您的类的数组
        gcPtr<FOO>obj= new obj[5];
        obj[2]->func();

    => 当您的对象不再被引用时,内存将被释放。例如

    gcPtr<FOO> obj1=new obj;
    // increment reference count for obj1 i.e refcount=1
     gcPtr<FOO> obj2=new obj; // increment reference count for obj2
    // i.e refcount=1
     obj1=obj2; // decrement reference count for obj1
    //i.e refcount=0 and increment
     // reference count for obj2 i.e refcount=2 . Memory will
    //be de-allocated
     // for obj1. Now obj1 to the same memory as obj2
     // When destructor for obj1 is called, it will decrement
    //the referencecount to 1, and
    //when destructor for obj2 is called, it will decrement it
    // to 0 and memory is de-allocated.

就是这样。集成垃圾回收并防止 C++ 内存泄漏,难道不是很简单吗?当然,是的。您只需要让您的类从 Refcount 派生。

如何为内置数据类型实现垃圾回收?

要为 intfloat 等内置数据类型实现垃圾回收,您可以编写一个简单的包装器来重载运算符。

class MyInt : public gcPtr<MYINT>
    {
      public:
        int* val;
    ..... // overloaded operators like ++,--,> etc
    ..... // or overload these operators in
          //RefCount base class        
// so that they will be available to any class inheriting         
// incorporating garbage collection
};

而不是每次都在您的类中添加所有运算符重载,您可以修改一次 RefCount 类并为 T 数据类型提供重载运算符,就像那里已经提供了一个重载运算符一样:即 T* operator->(void) { return p; }

垃圾回收实战

现在,让我们在一个简单的 MFC 对话框应用程序中看看垃圾回收的实际应用。在 Debug 模式下运行您的程序并开始调试。如果我使用了垃圾回收,这里是输出窗口的输出

    Loaded 'C:.DLL', no matching symbolic information found.
    Loaded 'C:.DLL', no matching symbolic information found.
    The thread 0x744 has exited with code 0 (0x0).
    The program 'D:.exe' has exited with code 0 (0x0).

没有内存泄漏。现在,如果我既不进行垃圾回收,也不手动删除对象,这里的输出是

    Loaded 'C:.DLL', no matching symbolic information found.
    Loaded 'C:.DLL', no matching symbolic information found.
    Detected memory leaks!
    Dumping objects ->
    {65} normal block at 0x002F2C80, 16 bytes long.
    Data: < ,/ > 00 00 00 00 80 2C 2F 00 CD CD CD CD CD CD CD CD 
    Object dump complete.
    The thread 0x50C has exited with code 0 (0x0).
    The program 'D:.exe' has exited with code 0 (0x0).

您可以看到内存转储。存在 16 字节的内存泄漏。

希望您喜欢这篇文章J。这是我在 Code Project 上的第一篇文章。如果您有任何建议或在使用这些类时遇到任何问题,请随时给我发电子邮件 + 如果您觉得这篇文章有用,请不要忘记投票给我 :)。

© . All rights reserved.