目录
auto_ptr作为成员之一的应用
auto_ptr的错误运用
auto_ptr之间不能共享拥有权
并不存在针对array而设计的auto_ptrs
auto_ptrs绝非一个“四海通用”的智能型指针
auto_ptrs不满足STL容器对其元素的要求
auto_ptrs的忠告
auto_ptr的运用实例
auto_ptr的实作细目
使用auto_ptr的一个最关键理由,就是使用了auto_ptr可以避免遗失资源。使用auto_ptr作为成员,当对象被删除时,auto_ptr也会自动删除其所指向的成员对象。
另外,在对象初始化的时候,auto_ptr也有其优势,按照普遍的new和delete的做法,只有当对象通过new的方式创建成功了,才可以进行后一步的delete,那如果在new的时候没创建成功,但同时有真的发生了占据内存的操作时,就无法析构占据的空间了。造成了内存资源的泄漏。但是,使用auto_ptr进行创建的时候,在对象初始化期间如果抛出异常,auto_ptr也可以帮助避免资源的遗失,这是new和delete搭配所做不到的。
比如,下面是一个按照普遍的new和delete的做法,进行的一个函数设计:
class ClassB {
private:
ClassA* ptr1;
ClassA* ptr2;
public:
ClassB(ClassA vall, ClassA val2)
:ptr1(new ClassA(val1)), ptr2(new ClassA(val2)) //构造函数:通过new,进行参数的初始化操作
{
}
ClassB(const ClassB& x)
:ptr1(new ClassA(*x.ptr1)), ptr2(new ClassA(*x.ptr2)) //拷贝构造函数:依旧是通过new,进行参数的初始化操作
{
}
const ClassB& operator= (const ClassB& x) //赋值操作符的重载
{
*ptr1 = *x.ptr1;
*ptr2 = *x.ptr2;
return *this;
}
~ClassB() {
delete ptr1;
delete ptr2;
}
};
这种设计方式带来的问题,之前说过了,就是,如果第一个new成功了,但第二个new却失败了,就会造成资源的遗失。只有当所有的new都创建成功了才可以,但凡有一个没成功,那所有的就都断了,已经创建的也无法再析构了。
再看使用auto_ptr来进行初始化,可以避免上面的问题:
class ClassB {
private:
const std::auto_ptr ptr1;
const std::auto_ptr ptr2;
public:
ClassB(ClassA val1, ClassA val2)
:ptr1(new ClassA(val1)), ptr2(new ClassA(val2))//构造函数,使用auto_ptr初始化
{}
ClassB(const ClassB& x)
:ptr1(new ClassA(*x.ptr1)),ptr2(new ClassA(*x.ptr2))//拷贝构造函数,使用auto_ptr初始化
{}
const ClassB& operator= (const ClassB& x)//赋值的操作符的重载
{
*ptr1 = *x.ptr1;
*ptr2 = *x.ptr2;
return *this;
}
};
但是,需要强调的是,即便auto_ptr可以实现不依靠析构函数来进行析构,但拷贝构造和赋值操作符重载也是需要进行重新编写的。如果不重写,那编译器会自己添加,那么,这两个操作都会转交拥有权,这恐怕就不一定是编程的人的意愿了。所以,如果在整个生命周期内的auto_ptr都不想改变其所指向对象的拥有权。最好还是可以选择const auto_ptr,来进行约束。
一个auto_ptr千万不能指向另一个auto_ptr(或其他对象)所拥有的对象。否则,当第一个指针删除该对象后,另一个指针突然间指向了一个已被销毁的对象,那么,如果再使用那个指针进行读写操作,就会引起一场灾难。
auto_ptr不可以指向array,这是因为auto_ptr的本质还是通过delete来进行删除的,而不是delete[]来释放其所拥有的对象的。而且,C++标准库中并未提供针对array而设计的auto_ptr。当然,标准库会另外提供一些容器来管理数据群。
并非是任何适合智能型指针的地方,都适合auto_ptr。特别提醒的是,如果是引用计数型的指针,如果有一组智能型指针指向同一个对象,那么,当且仅当最后一个智能型指针被销毁时,该对象才会被销毁。故这种场景下,并不适合用auto_ptr来指向。
比如,在拷贝动作和赋值动作之后,原本的auto_ptr和新产生的auto_ptr并不相等。原本的auto_ptr会交出控制权,而不是普遍意义上的拷贝给新的auto_ptr,所以,这个地方的拷贝和赋值函数就需要重新的定义了。一般的适用于其他元素的拷贝和赋值操作,就不适合auto_ptr所指向的类型。
有时候使用指向一个非常数的auto_ptr,并不比使用一个一般指针更安全。就比如当遇到引用计数型的场景的时候,就反而非常糟糕。而当我们有必要在不同容器之间共享元素时,这种指针非常有用了就。
根据之前的记述,下面对auto_ptr进行一个实际的操作,深切的体会到auto_ptr类似于一个常量指针,允许修改其所指向的内容,但不能修改其指针变量本身,下面这个展现auto_ptrs的转移所有权的行为:
#include
#include
using namespace std;
template
ostream& operator<< (ostream& strm, const auto_ptr& p)
{
if (p.get() == NULL)
{
strm << "NULL";
}
else
{
strm << *p;
}
return strm;
}
int main()
{
auto_ptr p(new int(42));
auto_ptr q;
cout << "after initialization:" << endl;
cout << "p:" << p << endl;
cout << "q:" << q << endl;
q = p;
cout << "after assigning auto pointers:" << endl;
cout << "p:" << p << endl;
cout << "q:" << q << endl;
*q += 13;
p = q;
cout << "after change and reassignment:" << endl;
cout << "p:" << p << endl;
cout << "q:" << q << endl;
system("pause");
}
运行结果:
下面再看,展示const auto_ptr的特性的例子:
#include
#include
using namespace std;
/* define output operator for auto_ptr
* - print object value or NULL
*/
template
ostream& operator<< (ostream& strm, const auto_ptr& p)
{
// does p own an object ?
if (p.get() == NULL) {
strm << "NULL"; // NO: print NULL
}
else {
strm << *p; // YES: print the object
}
return strm;
}
int main()
{
const auto_ptr p(new int(42));
const auto_ptr q(new int(0));
const auto_ptr r;
cout << "after initialization:" << endl;
cout << " p: " << p << endl;
cout << " q: " << q << endl;
cout << " r: " << r << endl;
*q = *p;
// *r = *p; // ERROR: undefined behavior
*p = -77;
cout << "after assigning values:" << endl;
cout << " p: " << p << endl;
cout << " q: " << q << endl;
cout << " r: " << r << endl;
// q = p; // ERROR at compile time
// r = p; // ERROR at compile time
system("pause");
}
运行结果:
这里来记录下auto_ptr的实现机理:
/* class auto_ptr
* - improved standard conforming implementation
*/
namespace std {
// auxiliary type to enable copies and assignments (now global)
template
struct auto_ptr_ref {
Y* yp;
auto_ptr_ref(Y* rhs)
: yp(rhs) {
}
};
template
class auto_ptr {
private:
T* ap; // refers to the actual owned object (if any)
public:
typedef T element_type;
// constructor
explicit auto_ptr(T* ptr = 0) throw()
: ap(ptr) {
}
// copy constructors (with implicit conversion)
// - note: nonconstant parameter
auto_ptr(auto_ptr& rhs) throw()
: ap(rhs.release()) {
}
template
auto_ptr(auto_ptr& rhs) throw()
: ap(rhs.release()) {
}
// assignments (with implicit conversion)
// - note: nonconstant parameter
auto_ptr& operator= (auto_ptr& rhs) throw() {
reset(rhs.release());
return *this;
}
template
auto_ptr& operator= (auto_ptr& rhs) throw() {
reset(rhs.release());
return *this;
}
// destructor
~auto_ptr() throw() {
delete ap;
}
// value access
T* get() const throw() {
return ap;
}
T& operator*() const throw() {
return *ap;
}
T* operator->() const throw() {
return ap;
}
// release ownership
T* release() throw() {
T* tmp(ap);
ap = 0;
return tmp;
}
// reset value
void reset(T* ptr = 0) throw() {
if (ap != ptr) {
delete ap;
ap = ptr;
}
}
/* special conversions with auxiliary type to enable copies and assignments
*/
auto_ptr(auto_ptr_ref rhs) throw()
: ap(rhs.yp) {
}
auto_ptr& operator= (auto_ptr_ref rhs) throw() { // new
reset(rhs.yp);
return *this;
}
template
operator auto_ptr_ref() throw() {
return auto_ptr_ref(release());
}
template
operator auto_ptr() throw() {
return auto_ptr(release());
}
};
}