Exception safe at what cost

From: Steven T. Hatton (susudata_at_setidava.kushan.aa)
Date: 08/05/04


Date: Thu, 05 Aug 2004 13:08:59 -0400

I read Stroustrup's article of the day:
http://www.research.att.com/~bs/C++.html

Programming with Exceptions. InformIt.com. April 2001.
http://www.research.att.com/~bs/eh_brief.pdf

Some of these ideas are finally beginning to sink in. I believe I looked at
the same article a while back and decided I wasn't quite ready for it. If I
understood things correctly, there seems to be a slight problem with the
design of his exception safe assignment operator. I believe I have
correctly extracted all the relevant code, and appropriately commented it
to describe the various issues discussed in the article. My questions are
in the final comment block.

class Vector { // v points to an array of sz ints
  int sz;
  int* v;
public: explicit Vector(int n); // create vector of n ints
  Vector(const Vector&);
  ~Vector(); // destroy vector
  Vector& operator=(const Vector&); // assignment
  int size() const;
  void resize(int n); // change the size to n
  int& operator[](int i); // subscripting
  const int& operator[](int i) const; // subscripting
};

Vector::Vector(int i) //constructor
  :sz(i) ,v(new int[i]) { } // establishes class invariant

Vector::~Vector() { delete[] v; }

int Vector::size() const { return sz; } //no effect on class representation

struct Bad_range { }; // I want an Über-exception to derive from!

int& Vector::operator[](int i)
{
  if (0<=i && i<sz) return v[i];
  throw Bad_range(); // no effect on class representation if thrown
}

/*
  This is a bad assignment operator implementation because
  it can lead to bad things like double deletion when exceptions
  are thrown. That's because it fails to maintain the class invariant
  that says a Vector holds an array
  
 */
Vector& Vector::operator=(const Vector& a)
{
  sz = a.sz; // get new size
  delete[] v; // free old memory
  v = new int[n]; // get new memory
  copy(a.v,a.v+a.sz,v); // copy to new memory
}

/*
  The is a better version because it maintains the invariant
  even if the memory allocation fails. We can probably trust
  copy() not to throw an exception, or to otherwise fail,
  so we don't worry about losing *p if it fails.
 */
Vector& Vector::operator=(const Vector& a)
{
  int* p = new int[n]; // get new memory [or thorw bad_alloc?]
  copy(a.v,a.v+a.sz,p); // copy to new memory

  /* invariant is violated in the next statement*/
  sz = a.sz; // get new size
  delete[] v; // free old memory

  /* invariant is reestablished in the next statement*/
  v = p;
}

/*
  This is the example of where the problem with the first form
  of Vector::operator=(const Vector& a) might arise. Vector::v is
  deleted once by the assignment operator, and then again when the
  destructor Vector::~Vector() is called upon exit of the containing
  block.
*/
int main() {
  try
    {
      Vector vec(10) ;
      cout << vec.size() << ´\n´; // so far, so good
      Vector v2(40*1000000) ; // ask for 160 megabytes
      vec = v2; // use another 160 megabytes
    }
  catch(Range_error) {
    cerr << "Oops: Range error!\n";
  }
  catch(bad_alloc) {
    cerr << "Oops: memory exhausted!\n";
  }
}

/*
  Now my observation is this:

  If I don't free the memory in Vector::v until the penultimate statement
  in the assignment operator function, then I will not be able to
  allocate as much memory as I would with the original (bad)
  implementation.

  Earlier in the article Stroustrup provides this example of a
  destructor: ~File_ptr() { if (p) fclose(p); }

  If I were to use the comparable ~Vector() { if(v) delete[] v; }
  in conjunction with the first form of the assignment operator
  function that would seem to solve the problem of not freeing
  potentially usable memory before allocating the new array.

  A problem with that approach seems to be that it violates the
  guarantee of class invariance. Is this a correct observation on my
  part? Is there a way to accomplish both the goal of freeing available
  memory prior to allocating more, and preserving the class invariant?
  I can think of some approaches, but they all seem to add overhead
  to the assignment operator function.
  
*/

-- 
STH 
Hatton's Law: "There is only One inviolable Law"
KDevelop: http://www.kdevelop.org  SuSE: http://www.suse.com
Mozilla: http://www.mozilla.org


Relevant Pages

  • Re: some c# questions!! please send me the answer
    ... TryParse first to see if the string is a valid DateTime before doing ... In .NET 1.1 all you can do is Prase and catch any exceptions ... Boxing is creating a "reference" shell around a value object so that it ... each "int" and then adds those object wrappers to the ...
    (microsoft.public.dotnet.csharp.general)
  • Re: Q about increment and assignment
    ... public static void main{ ... int i = 1; ... I understand the assignment operator happening before the ++, ... What is the difference between the original problem and this one? ...
    (comp.lang.java.programmer)
  • Re: Constructor initializations - which way better why?
    ... accepts an int. ... In the second case, A's default ctor is used to initialize it, ... and later an assignment operator will be used to set the value of A to 5. ... it is very likely that the first form will be ...
    (microsoft.public.vc.mfc)
  • Re: those darn exceptions
    ... is a pid that does not fit within sys.maxint. ... OverflowError: Python int too large to convert to C long ... very large int to something that's wrapping a C function. ... "what exceptions do you, my faithful tool, believe ...
    (comp.lang.python)
  • Re: Any benefit to programming a RISC processor by hand?
    ... |> According to my understanding Terje is interested in FP code, well, he ... turning exceptions off", even for integer arithmetic. ... int fred { ... imperfections cause by numeric exception handling. ...
    (comp.arch)