Managing Errors and Resources in Measurement Studio Visual C++

Publish Date: Sep 06, 2006 | 9 Ratings | 3.22 out of 5 |  PDF

Overview

This concept document is designed to provide you with a high-level overview of how you can design an efficient program by using a combination of exceptions and smart pointer classes.

Table of Contents

  1. Introduction
  2. Using Exceptions Instead of Error Codes
  3. Enabling and Disabling Exceptions in the Measurement Studio 488.2, VISA, and Analysis Libraries
  4. Preventing Resource Leaks
  5. Using the auto_ptr Template Class
  6. Designing Smart Pointer Classes
  7. Designing Efficient Code

1. Introduction

To provide users with an efficient, easy-to-use application, you include error handling in the code to ensure that the user is quickly and accurately informed of any error conditions that occur. Designing a program to efficiently handle errors requires extra code to handle the error, free any allocated resources that remain when the code exits, and return to the main program. The amount of extra code you use to accomplish these tasks depends upon how you design the error handling. In Visual C++, you can handle errors using error codes or exceptions.

When an error occurs in a program, the code might exit abnormally. If you do not design the code to properly free dynamically allocated memory, memory leaks might occur. Visual C++ provides the auto_ptr template class, which you can use to design code that automatically frees dynamically allocated memory. The auto_ptr template class is a smart pointer class. Smart pointer classes include dynamically allocated class objects, which you pass to the constructor, and deallocation in the destructor. Including dynamic allocation and deallocation in class constructors and destructors guarantees that any allocated memory associated with class objects is freed when the auto_ptr goes out of scope.

As an alternative to using the auto_ptr template class, you can design classes as smart pointer classes. To design a smart pointer class, you include dynamic allocation in the class constructor(s) and deallocation in the class destructor.

Measurement Studio includes smart pointer classes, such as the CNiVectorT and CNiMatrixT classes. Both classes include allocation and deallocation in their constructors and destructors. Refer to the NiVector.h, NiVector.inl, NiMatrix.h, and NiMatrix.inl files for these class definitions and implementations. These files are installed to the Measurement Studio\VC\Include directory, where Measurement Studio represents the location to which you installed Measurement Studio.

Back to Top

2. Using Exceptions Instead of Error Codes


Designing a program to use error codes to handle error conditions involves adding a substantial amount of code to detect the error, respond to it, free all resources, and inform the user about the error. To ensure that a program runs efficiently, National Instruments recommends you use exceptions rather than error codes to handle error conditions in the program.

When you use exceptions, you add try/catch blocks only where you need to capture an error condition, whereas if you use error codes, you must add if/else statements to test and handle various conditions for each function in the code. For example, suppose you have a function that calls three other functions. If you use error codes to handle errors, you must include code around each function to test for error conditions and respond to them appropriately, including freeing any allocated resources properly when the code exits. When an error occurs in a program that uses error codes, the user is informed about the error when the program returns an error code, which is usually an integer, and the user must research the error code to determine what type of error occurred. When you use exceptions, you include a try/catch block only where you need to capture errors. You can associate multiple catch blocks with one try statement, so you can customize the response to the error condition based on the type of error. Also, you can design exceptions to provide descriptive information about the error condition. So, when an error occurs, the user gets a description of the error condition immediately rather than having to research an error code.

Using exceptions is more efficient than using error codes because exceptions require less code than error codes. To properly handle errors, you must include code to send information to the appropriate location in the program when an error occurs. This information is typically an indication that an error occurred and a description of the error. When you use exceptions, the compiler automatically generates the code that sends the error information to the appropriate location in the program, thus returning focus to the appropriate place in the code. When you use error codes, you must write the code that indicates an error occurred, write the code to display the appropriate error code to the user, and write the code that sends this information to the appropriate location in the program.

The following table lists the differences between error codes and exceptions.

Error Codes
Exceptions
  • Repetitive error-identifying code around each function throughout the program
  • Added overhead for responding to the error and returning to the main program after an error is detected and addressed
  • Limited descriptive information about the error
  • Minimal extra code. One function handles the errors. Code calls this function wherever necessary.
  • Code functions to throw exceptions. The mechanism that propagates the exceptions to the appropriate location in the program is built into the compiler.
  • Because the exception is encapsulated in an object, you have control over the amount of descriptive information you provide.

Back to Top

3. Enabling and Disabling Exceptions in the Measurement Studio 488.2, VISA, and Analysis Libraries


The Measurement Studio 488.2, VISA, and Analysis libraries are configured to use exceptions for error handling. You can configure the Measurement Studio 488.2, VISA, and Analysis libraries to use error codes instead of exceptions by calling CNiException::SetExceptionMode(false). Use CNiException::GetExceptionMode() to determine which exception mode the libraries are currently using. Refer to the Measurement Studio Reference for information about the exceptions available for the Measurement Studio 488.2, VISA, and Analysis libraries. Click Start»Programs»National Instruments»Measurement Studio»Help»Measurement Studio Reference to access the Measurement Studio Reference.

The following table shows the same block of code with exceptions and error codes to demonstrate the difference between the two methods of error handling.

Note: The code samples below do not represent a valid use case. They are contrived only to demonstrate the differences between using error codes and exceptions.

Error Codes
Exceptions
    // The following code shows that exceptions
    // are turned off in  the 488.2, VISA,
    // and Analysis libraries.

    CNiException::SetExceptionMode(false);

    // The following code instantiates
    //a CNiVisaSession object. This
    // instantiation is purposefully
    // incorrect. It includes an
    // empty string for the VISA
    // resource name.


    CNiVisaSession instr("", VI_NULL, 5000);

    // The following code tests the object to
    // determine if it returned any errors during the
    // instantiation operation. If there was an error
    // during the instantiation operation, the program
    // displays a message box to inform the user of
    // the error. If there was no error during the
    // instantiation process, the program tests
    // whether the device allows direct memory access,
    // attempting to set the appropriate attribute on
    // the device. If there was no error during the
    // operation to set the attribute on the device,
    // the program proceeds. If there was an error
    // during the operation to set the attribute, the
    // program displays a message box to inform the
    // user of the error.
    if (instr.GetLastStatus() < 0)
    {

      CString message;
      instr.GetStatusDescription  (instr.GetLastStatus(), message);
      MessageBox(message);
      return;
    }
    else
    {
      if (instr.SetAttribute(VisaAllowDma, (ViBoolean)true) < 0)
      {
        CString message;
        instr.GetStatusDescription(instr.GetLastStatus(), message);
        MessageBox(message);
         return;
      }
      else
      {
        // Do something useful.
      }
    }
    //  The following code shows  that exceptions
    // are turned on in  the 488.2, VISA, and
    // Analysis libraries.

    CNiException::SetExceptionMode(true);

    // The following try/catch block creates
    // a CNiVisaSession object and throws
    // an  exception if the object is
    // not valid.

    try
    {

      // This instantiation is purposefully
      // incorrect. It includes an empty
      // string for the VISA resource name.

      CNiVisaSession instr("", VI_NULL, 5000);
      // Set an attribute on the device.
      instr.SetAttribute(VisaAllowDma, (ViBoolean)true);
    }
    catch (CException *e)
    {
      // Display the error for the user.
      e->ReportError();
      // Delete the exception class object.
      e->Delete();
    }
When you call CNiException::SetExceptionMode(false) to disable exceptions in the 488.2, VISA, and Analysis libraries, you must design any code that uses these libraries to handle error conditions with error codes. Using error codes includes adding code around each function to test for and respond to error conditions as well as free any allocated resources and return to the calling function. Refer to Measurement Studio Reference for a list of the error codes available in each of these libraries.

Because using exceptions is more efficient, National Instruments recommends you design code to use exceptions to handle errors.

Back to Top

4. Preventing Resource Leaks


Resource leaks occur when a system resource, such as a file or memory, is not freed after a program or block of code exits. The most common resource leak is a memory leak, which occurs when allocated memory is never freed. Memory leaks are often a result of the following conditions:
  • An error occurs, which causes the block of code to exit before resources are freed
  • A pointer goes out of scope or is reassigned before the memory it points to is deallocated
A memory leak can grow over time and eventually cause a system crash. For example, if a block of code in the program allocates memory but does not deallocate it and that block is executed several times before it exits, the memory leak continues to grow, taking up memory that other programs might try to use. Eventually, the memory leak causes the computer to run out of memory and crash. To avoid memory leaks, design the code to deallocate dynamically allocated memory when the code exits, including when the code exits unexpectedly as it does when it reaches an error condition. Use the Visual C++ keywords, new, delete, new[], and delete[], in combination with pointers, to allocate and deallocate memory on the heap. Alternatively, you can use the built-in Visual C++ smart pointer template class, auto_ptr, to guarantee that memory is allocated and deallocated correctly.

Note: You cannot use the auto_ptr template class with dynamically allocated arrays. The auto_ptr destructor calls delete, which does not delete arrays. Refer to the Microsoft Developer Network (MSDN) for more information about the auto_ptr template class.

Back to Top

5. Using the auto_ptr Template Class


Visual C++ provides the auto_ptr template class, which includes deallocation in its destructor, thus providing automatic cleanup for auto_ptr class objects. The following code sample is a modification of the Basic Visa example, which installs to the Measurement Studio\VC\Examples\Io\Visa\Basic Visa directory, where Measurement Studio is the location to which you installed Measurement Studio. The following code sample shows how to use the auto_ptr template class and shows the same code as it appears when you do not use the auto_ptr template class.

Note: The code samples below do not represent a valid use case. They are contrived only to demonstrate the differences between using auto_ptr and not using auto_ptr.

Using auto_ptr
Not using auto_ptr
void CBasicVisaDlg::OnButtonConfig()

{


    CString resStr;
    if(!resStr.IsEmpty())
    {
      try
      {
        // Create a CNiVisaSession
        // object.
        auto_ptr<CNiVisaSession>instr
           (new CNiVisaSession
           (resStr,VI_NULL,(uInt32)5000));

        // Do something with the object.
        instr->SetAttribute(VisaAllowDma,
               (ViBoolean)true);
      }
      catch (CNiVisaException* e)
      {
        // Display the error for the
        // user.
        e->ReportError();
        // Delete the exception class
        // object.
        e->Delete();
        // Not necessary to explicitly
        // delete the CNiVisaSession
        // instance because the auto_ptr
        // template class automatically
        // calls delete.
      }
    }
    else
    {
      MessageBox("Provide the VISA Resource Name
               in the Resource Name control.",
               "ERROR", MB_OK);
    }
}
void CBasicVisaDlg::OnButtonConfig()
{
    CString resStr;
    CNiVisaSession * instr;
    if (!resStr.IsEmpty())
    {
      try
      {
        // Creates a CNiVisaSession
        // object.
        instr = new CNiVisaSession(resStr, VI_NULL,(uInt32)5000);
        // Do something with the object.
        instr.SetAttribute(VisaAllowDma,(ViBoolean)true);
      }
      catch (CNiVisaException* e)
      {
        // Display the error for the
        // user.
        e->ReportError();
        // Delete the exception class
        // object.
        e->Delete();
        // Explicitly delete the
        // CNiVisaSession instance to prevent
        // a memory leak in the event
        // of an error condition.
        delete instr;
      }
    else
    {
      MessageBox("Provide the VISA Resource Name  
              in the Resource Name Control.",
             "ERROR", MB_OK);
    }
}
Using auto_ptr guarantees that the code frees memory when code exits. In the preceding example, the auto_ptr destructor frees memory automatically. It is not necessary to call delete to free the memory that the auto_ptr manages. Refer to MSDN for more information about the auto_ptr template class.

Back to Top

6. Designing Smart Pointer Classes


As an alternative to using the auto_ptr template class, you can design classes to be smart pointer classes by including dynamically allocated class objects in the constructor and deallocation in the destructor. To provide easy resource management, Measurement Studio includes smart pointer classes, such as the CNiVectorT class. The CNiVectorT class includes typedefs, such as CNiReal64Vector, that you use to instantiate various vectors. For example, you use CNiReal64Vector to instantiate a vector of doubles. Because CNiVectorT is a smart pointer class, when you instantiate a CNiReal64Vector vector, you construct a double[] object that is dynamically allocated on the heap. When the program finishes using the vector, it calls the CNiVectorT destructor to deallocate the memory that the constructor allocated for the double[] object.
The following code samples show how the CNiVectorT class includes dynamic allocation in the constructor and deallocation in the destructor:

// CNiVectorT constructor that passes an int for the vector size.
template <class DataType>
CNiVectorT<DataType>::CNiVectorT (unsigned int size)
{

    // Calls the Init function to initialize the vector.
    Init(size);
}

// Implementation for the Init function.
template <class DataType>
void CNiVectorT<DataType>::Init (int size)
{
    // Initializes the elements of the vector to NULL. vectorData represents
    // the type of vector you are working with. For example, if you are using
    // the CNiReal64Vector data type, the type of vectorData is double *.
    vectorData = NULL;
    // Call to the SetSize function, which dynamically allocates the vector on
    // the heap.
    SetSize(size);
}

// Implementation for the SetSize function.
template <class DataType>
void CNiVectorT<DataType>::SetSize (unsigned int newSize)
{
    // The GetSize function returns the size value set in the Init function.
    const unsigned int oldSize = GetSize();
    //-------------------------------------------------------------------------
    //  No size change?
    //-------------------------------------------------------------------------
    if (newSize == oldSize)
    {
      return;
    }
    //-------------------------------------------------------------------
    // Delete the previous vector data, then allocate and initialize the new
    //  vector data.
    //-------------------------------------------------------------------------
    delete [] vectorData;
    vectorData = NULL;
    SetSizeExtents(newSize);
    if (newSize != 0)
    {
      // Dynamically allocates the vector, of the appropriate data type, on the heap.
      vectorData = new DataType[newSize];
    }
}
// CNiVectorT destructor.
template <class DataType>
CNiVectorT<DataType>::~CNiVectorT ()
{

    // Deletes the dynamically allocated vector.

    delete [] vectorData;


}

When you use exceptions to handle errors in code, you must design the code to anticipate functions exiting at any point during program execution. When you use a combination of exceptions and smart pointer classes, you can handle errors where they occur and address them and free resources as soon as the code exits.

In addition to being a smart pointer class, the CNiVectorT class implementation includes the use of exceptions to handle error conditions. For example, if a vector index is less than zero, the CNiVectorT class throws a CNiVectorAccessException. The following code shows how the CNiVectorT class uses the CNiVectorAccessException class:

// This operator overload handles CNiVectorT vectors with an integer index.
template <class DataType>
DataType CNiVectorT<DataType>::operator [] (int index) const
{
    // Test the index to determine if it is less than zero and if it is,
    // throw an exception.
    if (index < 0)
    {
      throw new CNiVectorAccessException(true);
    }
    return operator[] ((unsigned int)index);
}

// This operator overload handles CNiVectorT vectors with a unsigned int index.
template <class DataType>
DataType CNiVectorT<DataType>::operator [] (unsigned int index) const
{
    // Test the index to determine if it is greater than or equal to the size of
    // the vector, and if it is, throw an exception.
    if (index >= GetSize())
    {
      throw new CNiVectorAccessException(*this,index,true);
    }
    return vectorData[index];
}

Back to Top

7. Designing Efficient Code


Designing code to use a combination of exceptions and smart pointer classes ensures that a program works efficiently. When you use exceptions rather than error codes, you use less code to detect and handle errors. Also, you can design the exceptions to include code that frees resources and provides expanded descriptions to accurately inform the user about the error condition. When you use smart pointer classes, you eliminate the chance for memory leaks. To help you design efficient test and measurement applications, Measurement Studio includes smart pointer classes, such as the CNiVectorT and CNiMatrixT classes, which also include the use of exceptions in their implementations to detect and handle errors. Additionally, the Measurement Studio 488.2, VISA, and Analysis libraries are configured to throw exceptions to handle errors that occur while using class objects from these libraries.

Back to Top

Bookmark & Share

Ratings

Rate this document

Answered Your Question?
Yes No

Submit