auto_ptr 及其用法






2.89/5 (6投票s)
本文讨论了 STL 的 auto_ptr 类的用法
引言
本文的主要目的是教育新手如何在 C++ 中编写无内存泄漏的程序。
我考虑撰写一篇关于 auto_ptr 的文章已经很久了。当我开始学习 auto_ptr 时,我发现许多文章描述了 auto_ptr 究竟是什么,它们的用途以及如何使用它们编写代码。坦率地说,我找不到任何深入解释 auto_ptr 的文章。最后,我决定写一篇文章,作为对网络上现有文章的补充。
首先要了解 auto_ptr,最显而易见的问题是 auto_ptr 是什么?
Auto 指针作为 STL 标准命名空间的一部分提供。auto_ptr 是一个模板类,主要用于封装任何动态创建的对象,最好使用 new 运算符。Auto 指针充当自由存储区上动态创建对象的容器,一旦分配内存块,您就不需要真正关心释放内存,因为 auto 指针会在封装的对象超出作用域且可能不再需要时负责释放该内存区域。
让我们看一下下面的不安全代码
void myFunction()
{
T* ptr = new T;
/*... code...*/
delete ptr;
}
我们大多数人每天都编写这样的代码,如果它没有做任何异常的事情,这段代码会运行得很好。如果我的函数从未执行 delete 语句会发生什么?可能是由于导致控制由于函数返回而返回调用者的条件,或者由于在函数执行期间抛出的异常。这是一个我们可以遇到经典内存泄漏问题的理想场景。
现在,您脑海中第一个浮现的问题是如何处理这种困难的情况。这个问题的答案是 auto 指针,我们可以将上面代码中声明的指针 ptr 包装到一个智能指针即 auto 指针对象中,并忘记内存泄漏问题。现在,即使代码执行没有到达 delete 语句,我们也可以确保在函数的堆栈展开时,ptr 变量指向的内存会被清理。
让我们看一下修正后的 myFunction 函数
#include <memory>
using namespace std;
void myFunction()
{
auto_ptr ptr( new T );
/*... code...*/
// ptr’s destructor gets called as the function’s stack unwinds here & the object
// gets deleted automatically
}
在上面的代码中,我们甚至不必显式调用 ptr 包含的指针上的 delete。auto 指针会在销毁时自动释放包含对象的内存。
使用 auto_ptr 与使用内置指针相同,要“取回”资源并再次承担手动所有权,我们只需调用 release()
class CMyClass
{
private:
int m_nData;
public:
CMyClass()
{
m_nData = 0;
}
CMyClass(int nData)
{
m_nData = nData;
}
void SetValue(int nData)
{
m_nData = nData;
}
void PrintValue()
{
printf("Value is: %d\n",m_nData);
}
CMyClass& operator =(int nData)
{
m_nData = nData;
return *this;
}
};
void myFunction()
{
CMyClass* ptr1 = new CMyClass();
// right now, we own the allocated object
// pass ownership to an auto_ptr
auto_ptr ptr2( ptr1 );
// use the auto_ptr the same way
// we'd use a simple pointer
*ptr2 = 12; // just as writing "*ptr1 = 12;"
ptr2->PrintValue(); // just as writing "ptr1->PrintValue();"
// use get() to see the pointer value
assert( ptr1 == ptr2.get() );
// use release() to take back ownership
CMyClass* ptr3 = ptr2.release();
// delete the object ourselves, since now
// no auto_ptr owns it any more
delete ptr3;
}
我们可以使用 auto_ptr 的 reset() 函数将 auto_ptr 重置为拥有不同的对象。但是,如果 auto_ptr 已经拥有一个对象,它会首先删除已拥有的对象,因此调用 reset() 与销毁 auto_ptr 并创建一个拥有新对象的新 auto_ptr 几乎相同。
void myFunction()
{
auto_ptr ptr( new CMyClass(1) );
ptr.reset( new CMyClass(2) );
// deletes the first CMyClass that was
// allocated with "new CMyClass (1)"
} // finally, ptr goes out of scope and
// the second CMyClass is also deleted
Now comes the main part that I discussed in the begining of this article, I have not seen any real world example describing use of auto_ptr as a class level variable.
class CName
{
public:
CName()
{
m_pName = NULL;
}
~CName()
{
delete[] m_pName;
}
void SetupName(int nSize)
{
m_pName = new char[nSize];
}
void SetName(char* pName)
{
strcpy(m_pName,pName);
}
char* GetName()
{
return m_pName;
}
private:
char* m_pName;
};
class CEmployee
{
public:
CEmployee()
{
m_ptrName = new CName();
}
~CEmployee()
{
delete m_ptrName;
}
void Name(char* pName)
{
m_ptrName->SetName(pName); // Runtime Exception: memory should have been
// allocated before setting name by using syntax
// m_ptrName->SetupName(<size>);
}
void PrintName()
{
printf("Name: %s\n",m_ptrName->GetName());
}
private:
CName* m_ptrName;
};
void myFunction()
{
CEmployee objEmp;
objEmp.Name("Neeraj"); // Exception
objEmp.PrintName();
}
上面的代码肯定会导致运行时异常,因为在设置员工姓名时没有通过调用 SetupName 函数来分配内存。程序执行在发生异常时肯定会停止,如果您仔细查看代码,就会发现其中存在一个经典的内存泄漏,因为 CEmployee 类在其构造函数中使用 operator new 创建 CName 的实例。由于代码执行从未到达这一点,因此这段分配的内存永远不会被释放。我们当然可以通过将 CName 的 ptr 封装到 auto_ptr 对象中来使内存分配异常安全,下面的代码确保描绘了 CEmployee 类的正确代码。
class CEmployee
{
public:
CEmployee()
{
m_ptrName = auto_ptr(new CName());
}
~CEmployee()
{
CName* ptr = m_ptrName.release();
delete ptr;
}
void Name(char* pName)
{
m_ptrName->SetName(pName); // Runtime Exception: memory should have been
// allocated before setting name by using syntax
// m_ptrName->SetupName(<size>);
}
void PrintName()
{
printf("Name: %s\n",m_ptrName->GetName());
}
private:
auto_ptr m_ptrName;
};
上面的代码创建了一个 CEmployeeName 类的 auto_ptr 对象成员。通过这样做,我们确保即使发生异常,m_ptrName 对象包含的内存也会被释放。仔细查看构造函数和析构函数,类的构造函数创建一个 CName 类实例并将所有权传递给类的成员 CName auto_ptr m_ptrName。在析构函数中,我们已经取回了 auto_ptr 包含的 CName 类指针的所有权,我们通过调用 delete 安全地删除了它。
当一个 auto_ptr 的所有权从一个 auto_ptr 传递到另一个 auto_ptr 时。如果左值 auto_ptr 对象已经包含一个有效的 ptr,则 auto_ptr 首先释放它持有的内存,然后将右值 ptr 附加到它。