现在的位置: 首页 > 综合 > 正文

EXCEPTION HANDLING:A FALSE SENSE OF SECURITY

2013年11月30日 ⁄ 综合 ⁄ 共 4340字 ⁄ 字号 评论关闭

EXCEPTION HANDLING:
A FALSE SENSE OF SECURITY

by Tom Cargill

 

This article first appeared in C++ Report, Volume 6, Number 9, November-December 1994.

 

 

 

I suspect that most members of the C++ community vastly underestimate the skills needed to program with exceptions and therefore underestimate the true costs of their use. The popular belief is that exceptions provide a straightforward mechanism for adding reliable error handling to our programs. On the contrary, I see exceptions as a mechanism that may cause more ills than it cures. Without extraordinary care, the addition of exceptions to most software is likely to diminish overall reliability and impede the software development process.

This ""extraordinary care"" demanded by exceptions originates in the subtle interactions among language features that can arise in exception handling. Counter-intuitively, the hard part of coding exceptions is not the explicit throws and catches. The really hard part of using exceptions is to write all the intervening code in such a way that an arbitrary exception can propagate from its throw site to its handler, arriving safely and without damaging other parts of the program along the way.

In the October 1993 issue of the C++ Report, David Reed argues in favor of exceptions that: ""Robust reusable types require a robust error handling mechanism that can be used in a consistent way across different reusable class libraries."" While entirely in favor of robust error handling, I have serious doubts that exceptions will engender software that is any more robust than that achieved by other means. I am concerned that exceptions will lull programmers into a false sense of security, believing that their code is handling errors when in reality the exceptions are actually compounding errors and hindering the software.

To illustrate my concerns concretely I will examine the code that appeared in Reed's article. The code (page 42, October 1993) is a Stack class template. To reduce the size of Reed's code for presentation purposes, I have made two changes. First, instead of throwing Exception objects, my version simply throws literal character strings. The detailed encoding of the exception object is irrelevant for my purposes, because we will see no extraction of information from an exception object. Second, to avoid having to break long lines of source text, I have abbreviated the identifier current_index to top. Reed's code follows. Spend a few minutes studying it before reading on. Pay particular attention to its exception handling. [Hint: Look for any of the classic problems associated with delete, such as too few delete operations, too man4ddelete operations or access to memory after its delete.]

 

template

class Stack

{

  unsigned nelems;

  int top;

  T* v;

public:

  unsigned count();

  void push(T);

  T pop();

 

  Stack();

  ~Stack();

  Stack(const Stack&);

  Stack& operator=(const Stack&);

};

 

template

Stack::Stack()

{

  top = -1;

  v = new T[nelems=10];

  if( v == 0 )

    throw ""out of memory"";

}

 

template

Stack::Stack(const Stack& s)

{

  v = new T[nelems = s.nelems];

  if( v == 0 )

    throw ""out of memory"";

  if( s.top > -1 ){

    for(top = 0; top <= s.top; top++)

      v[top] = s.v[top];

 

    top--;

  }

}

 

template

Stack::~Stack()

{

  delete [] v;

}

 

template

void Stack::push(T element)

{

  top++;

  if( top == nelems-1 ){

    T* new_buffer = new T[nelems+=10];

    if( new_buffer == 0 )

      throw ""out of memory"";

    for(int i = 0; i < top; i++)

      new_buffer[i] = v[i];

    delete [] v;

    v = new_buffer;

  }

  v[top] = element;

}

 

template

T Stack::pop()

{

  if( top < 0 )

    throw ""pop on empty stack"";

  return v[top--];

}

 

template

unsigned Stack::count()

{

  return top+1;

}

 

template

Stack&

Stack::operator=(const Stack& s)

{

  delete [] v;

  v = new T[nelems=s.nelems];

  if( v == 0 )

    throw ""out of memory"";

  if( s.top > -1 ){

    for(top = 0; top <= s.top; top++)

      v[top] = s.v[top];

    top--;

  }

  return *this;

}

My examination of the code is in three phases. First, I study the code's behavior along its ""normal,"" exception-free execution paths, those in which no exceptions are thrown. Second, I study the consequences of exceptions thrown explicitly by the member functions of Stack. Third, I study the consequences of exceptions thrown by the T objects that are manipulated by Stack. Of these three phases, it is unquestionably the third that involves the most demanding analysis.

 

Normal Execution Paths

Consider the following code, which uses assignment to make a copy of an empty stack:

 

Stack y;

Stack x = y;

assert( y.count() == 0 );

printf( ""%u/n"", x.count() );

 

 

 

17736

The object x should be made empty, since it is copied from an empty master. However, x is not empty according to x.count(); the value 17736 appears because x.top is not set by the copy constructor when copying an empty object. The test that suppresses the copy loop for an empty object also suppresses the setting of top. The value that top assumes is determined by the contents of its memory as left by the last occupant.

Now consider a similar situation with respect to assignment:

 

Stack a, b;

a.push(0);

a = b;

printf( ""%u/n"", a.count() );

抱歉!评论已关闭.