由一份auto_ptr源代码所引发的思考

 由一份auto_ptr源代码所引发的思考
   Kyle
   CPPCN 版权所有
  如果我问你,auto_ptr最关键的地方,或者说它与一般指针最不一样的地方在哪里,你一定会说它是一个对象,它与自己所占有的资源密切相关,当自己消亡的时候,它也会将自己所拥有的资源一同释放。很好,它之所以会拥有这一特性,全都要归于析构函数的功劳,在析构函数中,它会进行善后处理,这样也就很好的避免了资源泄漏。当然,引入auto_ptr的原因还不至于这么简单,因为许多资源泄漏的问题主要是由于我们的粗心大意所致,所以只要我们细心一些,还是会避免一些不该发生的资源泄漏问题。那究竟是什么原因让我们引入auto_ptr呢?对了,你一定也想到了,就是在异常处理的时候。Ok,来看下面这个例子:
  void foo()
  {
  ClassA *ptr=new ClassA;
  try
  {
  /* 此处放置可能抛出异常的操作 */
  }
  catch(…)
  {
   delete ptr;
   throw;
  }
  delete ptr;
  }
  上面这个例子使用了一般的指针,你会发现它防止发生资源泄漏的处理是多么复杂,必须在每个catch中进行资源的释放,而且有可能会有许多的catch。天哪,这简直就是灾难,它会使我们的代码很长,不易维护,如果忘记其中的一个,就会产生莫名其妙的错误。既然一般的指针防止资源泄漏会如此的繁琐,那有没有一个办法可以使我们不必操心资源的释放呢,由于在异常发生的时候,会进行堆栈解退,所以我们不必担心作为局部变量的指针本身不被销毁,既然这样,我们干脆建立一个指针对象,好比下面这样:
  template<class T>
  class auto_ptr1
  {
  private:
  T* ap;
  public:
   ……..
  ~auto_ptr(); //资源释放
  }
  当指针被销毁的时候,必然会执行析构函数,那就在析构函数中进行资源的释放不就ok了,呵呵,怎么样,是不是很简单呢?的确,整个逻辑的确很简单,但是如果我们在深入思考一下这个指针对象的特性的话,我们会发现有一个困难的问题等待我们去解决。那下面就让我们来看看会遇到什么困难。
   由于在auto_ptr销毁的时候它会自动通过析构函数释放所拥有的资源,那么也就决定了auto_ptr对于资源的独占性,即一个资源只能被一个auto_ptr所指向。这一点应该很好理解,假设有两个auto_ptr指向同一个资源,那当其中一个被销毁的时候,另一个将会指向哪里呢?这种指针往往是最为危险的。既然这样,我们怎样才能保证这种独占性呢,其实也很简单,当对指针进行赋值和复制的时候,剥夺原有指针对资源的拥有权,问题也就迎韧而解了。就好比这样:
  auto_ptr<int> p(new int(20));
  auto_ptr<int> q;
  q=p; //p已经丧失了对资源的拥有权,q现在是p的主人 
  对于这种一般性的情况,问题似乎已经解决了,下面让我们来看一种特殊但却合理的情况: 
  auto_ptr<int> foo()
  {
  auto_ptr<int> p(new int(20));
  return p;
  }
  int main()
  {
  auto_ptr<int> q(foo());
  return 0;
  }
  你认为上面这种情况怎么样,它是合理的,因为它实现了资源的顺利移交,但是你认为auto_ptr<int> q(foo());这一句应该怎样才能调用成功呢?为了说清这个问题,还需要说说左值和右值以及临时对象的问题。也许你会说左值应该就是能够改变的变量,右值当然就是不能改变的变量喽!对吗?对了一点点,实际上左值是能够被引用的变量,说的通俗点就是有名字的变量,你一定想到了些什么,对了,临时变量就没有名字,即使有你也不会知道,因为它不是由你创建的,编译器会在内部辨别它,但你并不知道,因此临时变量不是左值,而是右值。你也许还会问,那const变量是不是左值呢?根据定义,它有名字,当然就是左值了。因此左值并非一定“可被修改”。但是左值和右值与参数有什么关系吗?我要告诉你的是:有,而且相当密切,因为标准c++规定:若传递给类型为引用的形参的实参是右值的话必须保证形参为const引用。Ok,现在让我们回到原来的问题上,由于foo()按值返回,因此编译器必然会产生一个临时对象,也就是说 auto_ptr<int> q(foo()); 这一句中q的构造函数传入的参数是一个右值,因此若想让这一句成功的调用,它的原型必须是这样的:auto_ptr(const auto_ptr&); 但是这样行吗?显然是不行的,因为我们还要剥夺原有指针对资源的拥有权呢,如果采用const引用,那是无法进行剥夺的,因为你无法修改它。你也许想到了另一个办法,我们只要用mutable修饰核心数据域的话,那么即使它是const也可以进行修改它的核心数据。这个办法看起来似乎不错,但如果我们在考虑一下下面这种情况,你或许会改变你的看法,假设有一个const auto_ptr ,如果我们把它赋值给另一个auto_ptr的话,你说应该是怎样的情况发生,呵呵,当然应该是禁止了,因为你不应该试图去改变一个const对象,即禁止剥夺一个const auto_ptr对资源的拥有权。但是如果按照你的想法,采用mutable的话,这种改变是可以实现的,因此你应该打消采用mutable的念头。那么难道就没有办法了吗?当然有,但是不太容易想到,请看下面这个简单的例子: 
  class X
  {
  private:
  int value;
  public:
  X(int v=0)
  {
  value=v;
  }
  X(X& a)
  {
  value=a.value;
  a.value=0;
  }
  int set(int v)
  {
  value=v;
  return value;
  }
  friend ostream& operator << (ostream& os, const X& x)
  {
  os<<x.value<<endl;
  }
  }; 
  X f()
  {
  X a(100);
  return a;
  } 
  int main()
  {
  X c(f());
  cout<<c;
  return 0;
  }
  上面这个例子和我们所遇到的情况有些相似X c(f());这一句是无法调用成功的,而且也不能把复制构造函数的引用参数变为const,因为我们要修改参数。Ok,我们就利用这个简单的例子来解决我们所遇到的问题。既然我们已经想到的一些方案不能达到我们的目的,那我们怎么做呢,对了,我们可以用类型转换函数。下面让我来帮你整理一下思路:
  1.我们首先应该先定义一个类型转换层,它的核心数据应该和X的核心数据一样,例如:
  struct Y
  {
  int val;
  Y(int v):val(v){}
  };
  有了这个转换层,我们就可以先把函数f()返回时所生成的临时对象通过一个从X到Y的转换函数转型到Y。然后再通过一个从Y到X的转换函数进行对象的构造。至此,所有问题都得以解决。下面让我们来看一下具体方法。
  2.添加一个从X到Y的类型转换函数。如下:
  operator Y()
  {
  Y y(value);
  return y;
  } //成员转换函数,c++ primer第15章
  3.添加一个从Y到X的类型转换函数,即只有一个参数的构造函数。 
  X(Y a)
  {
  value=a.val;
  }
  OK,大功告成,你可以把这个例子在你的编译器上实现,果然能够解决所有的问题(在VC上会有一点问题,因为VC在临时对象这一点上对标准C++的支持不够好,用临时对象作参数的时候不加const也可以编译通过),下面我给出auto_ptr的一个实作范例,我想你应该能够理解它了:) 
  template<class Y>
  struct auto_ptr_ref
  {
  Y* yp;
  auto_ptr_ref(Y* rhs):yp(rhs){}
  }; //注意这个转换层 
  template<class T>
  class auto_ptr1
  {
  private:
  T* ap;
  public:
  typedef T element_type; 
  explicit auto_ptr1(T* ptr=0) throw():ap(ptr){} 
  auto_ptr1(auto_ptr1& rhs) throw():ap(rhs.release()){} 
  template<class Y>
  auto_ptr1(auto_ptr1<Y>& rhs) throw():ap(rhs.release()){} 
  auto_ptr1& operator = (auto_ptr1& rhs) throw()
  {
   reset(rhs.release());
   return *this;
  } 
  template<class Y>
  auto_ptr1& operator = (auto_ptr1<Y>& rhs) throw()
  {
   reset(rhs.release());
   return *this;
  } 
  ~auto_ptr1() throw()
  {
   delete ap;
  } 
  T* get() const throw()
  {
   return ap;
  } 
  T& operator *() const throw()
  {
   return *ap;
  } 
  T* operator ->() const throw()
  {
   return ap;
  }
  T* release() throw()
  {
   T* tmp(ap);
   ap=0;
   return tmp;
  } 
   void reset(T* ptr=0) throw()
  {
  if(ap!=ptr)
  {
   delete ap;
   ap=ptr;
  }
  } 
  auto_ptr1(auto_ptr_ref<T> rhs) throw():ap(rhs.yp){} 
  auto_ptr1& operator = (auto_ptr_ref<T> rhs) throw()
  {
   reset(rhs.yp);
   return *this;
  } 
   template<class Y>
  operator auto_ptr_ref<Y>() throw()
  {
   return auto_ptr_ref<Y>(release());
  } 
  template<class Y>
  operator auto_ptr1<Y>() throw()
  {
   return auto_ptr1<Y>(release());
  }
  }; 
  好了,今天就说到这吧,大家有什么问题可以提出

你可能感兴趣的:(c,struct,OS,delete,Class,编译器)