home account info subscribe login search FAQ/help site map contact us


 
Brief Full
 Advanced
      Search
 Search Tips
[an error occurred while processing this directive]
Previous Table of Contents Next


Comments

You’re probably asking yourself, “Why have two different access methods, namely operator [] and the at() member function, to perform the same operation?” Seemingly, this duality is redundant. However, it isn’t. In real-world situations, two additional factors not addressed so far will influence your design: performance and safety. This is where these accessing methods differ.

The overloaded [] operator was designed by the C++ Standardization Committee to be as efficient as the subscript operator of a built-in array was. Therefore, it does not check whether its argument actually refers to a valid member. Using an illegal subscript yields undefined behavior. However, the lack of runtime checks also ensures the fastest access time (the overloaded operator [] is usually inline, so the overhead of a function call is avoided). When performance is paramount, and when the code is written carefully so that only legal subscripts are accessed, you should use the subscript operator. It is also more readable and intuitive.

Nonetheless, runtime checks are unavoidable under some circumstances. at() is less efficient than operator [], but it is safer because it performs range checking. In an attempt to access an out-of-range member, it throws an exception of type std::out_of_range. Indeed, the samples did not have any try blocks or catch statements surrounding every invocation of at(). This is because the samples were carefully crafted and tested not to access an out-of-range subscript. In large-scale projects, that cannot always guaranteed. Sometimes the subscript argument is received from an external source: a function, a database record, a network message, or a human operator. Keeping track of every vector object and its current valid number of members is impractical in large applications. In these cases, it is advisable to let the underlying mechanism handle access violations in an automated and well-behaved manner; for example, throw an exception rather than let the program corrupt its memory, or crash.

When at() is used, your code has to handle a std::out_of_range exception, as in the following example:

#include <vector>
#include <iostream>
#include <string>
using namespace std;
void main()
{
  try
  {
     vector<string> vs; // vs has no elements currently
    cout<< vs.at(0) <<endl; //oops! no elements exist in vs; exception thrown
  }
  catch(std::out_of_range & except)
  {
    // diagnostics and remedies for an out-of-range subscript
  }
  catch(...) //handle all other exception, e.g., memory exhausted
  {
    // do something
  }
}//end main

Note that exception handling incurs runtime overhead and larger executable size. C++ leaves you the choice.

7.4 Use a generic LIFO data model?

Problem

I need a data model that simulates a function call chain. The first function calls another one and so on, until the lowermost function has been called. When the lowermost function exits, control is returned to its caller, and so forth.

Technique

A stack, also termed last-in-first-out (LIFO), is an ideal data model to implement a function call chain. This How-To will use the STL’s stack container.

A comprehensive explanation of how to implement a full-blown function call mechanism or how to simulate an exception handling mechanism is well beyond the scope of this book. This How-To only focuses on the data model aspects of the implementation.

Exception handling (EH) in C++, as you will read in the following chapters, works in a LIFO model. When an exception is thrown by a function, the EH mechanism tries to locate a corresponding handler (a catch statement) for this particular exception in the current function. If such a handler does not exist, the exception is propagated to the function one level higher in the calling chain—that is, the function from which the current one was called and the current function is exited. This process is repeated until a handler is reached or the program is aborted.

The highest function in the calling chain, main(), is the first element pushed into the stack. Next is a function called from main() that is pushed into the call stack, and so on. When an exception is thrown and no appropriate handler can be found, the stack is popped, meaning the lowermost function in the calling chain is removed from the stack and control is returned to the caller. This process is reiterated until the stack has been emptied, which in our case means program abortion.

Steps

1.  Change to your base source directory and create a new project named STACK_DEMO.
2.  Start your source code editor and type the following code into a file named CALLCHAIN.H:
#include <string>
#include <stack>
using namespace std;

enum status { success, failure }; // return codes

class CallChain {

private:
  stack <string> st;

protected:
  bool CallChain::HandlerExists(const string& excep);
  void ExitCurrentScope();

public:
  CallChain();
  ~CallChain();
  status Call(const string& function);
  void Throw(const string& excep);
};
3.  Save CALLCHAIN.H. Create a new file named CALLCHAIN.CPP under the same project and type the following code into that file:
#include <iostream>
#include “callchain.h”
using namespace std;

CallChain::CallChain()
{
  st.push(“main”);  // first we push main onto the stack
  cout<<”pushed main; total “<<st.size()<<endl;
}

CallChain::~CallChain()
{
  if (!st.empty() )  //pop main if it hasn’t been popped before
    st.pop();
}

status CallChain::Call(const string& function)
{
  st.push(function); //call another function
  cout<<”pushed “ <<function<< “; total: “<< st.size() <<endl;
  if (function == “bad_func”)  //is it the offending one?
    return failure;
  return success;
}


void CallChain::Throw(const string& excep)
{
  while (!st.empty()) //unwind the stack
  {
    if (! HandlerExists(excep))
      ExitCurrentScope(); //terminate current function
    else
      break; // a handler for the current exception was found
  }
}

bool CallChain::HandlerExists(const string& excep)
{
  return false; //in this simulation, no handlers exist
}

void CallChain::ExitCurrentScope()
{
 string func = st.top(); // display the last function called
 st.pop(); //  remove it
 cout<<”popped “ << func <<”; total “<< st.size() <<endl;
}


Previous Table of Contents Next


Products |  Contact Us |  About Us |  Privacy  |  Ad Info  |  Home

Use of this site is subject to certain Terms & Conditions, Copyright © 1996-1999 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permision of EarthWeb is prohibited.