Lab 7: Exception Handling

Topics: Try/Catch Statements, noexcept

Group work: Group work is allowed for the labs, but each person must do their own coding and each person must turn in an assignment. Copying other peoples' code / code files is not allowed. Copied assignments will receive 0% for everybody involved.

Assignments must build: Assignments that don't build will automatically just be given a flat 50% score. I may still give feedback on what went wrong, and 50% is better than 0%, so turn in something - but ideally, make sure it builds.


 

About

Exception handling helps us design better code that is intended to be reused over and over. When creating a data structure that other programmers will be using, it can be important to report errors with your structure to them so that they can decide how to resolve the error; you can’t always make the best design decisions on your own, it’s up to each programmer to figure out the needs for their project.

You will start with the following code, which contains an object that handles file streams, as well as the main source file that contains tests. At the moment, it should compile and run, but it will crash during the tests because the RecordManager object doesn’t have error checking.

In this lab, you will implement the exception handling within the Record- Manager, as well as checking for exceptions from the main file.


 

Setting up the project

In your Student Repository, open the CS250-Lab07-Exceptions project. It will have the following files:

  • main.cpp
  • RecordManager.hpp
  • RecordManager.cpp

 

Adding error checking

Within the RecordManager class functions, you will add error checking and throw commands. Outside the class, in main and the tests, you will add the try/catch blocks.

Throw, Try, and Catch

INSIDE a function is where the throw command is used. After checking for some error state, the response is to throw the type of exception that has occurred, as well as an error message.

OUTSIDE a function (at the function-call level) is where try and catch commands are used. When a function that may throw an exception is being called, it should be wrapped within a try block. Then, the catch block(s) follow, to catch different types of exceptions, resolve them, clean up, and allow the program to continue.


Updating RecordManager::OpenOutputFile

In this function, add an error check before m_outputs[ index ] is accessed: Check to see if the index value is equal to -1. If so, throw a runtime error like this:

if ( index == -1 )
{
    throw runtime_error( "No available files" );
}

Updating RecordManager::CloseOutputFile

This function should also throw a runtime error if the returned index is -1.


Updating RecordManager::WriteToFile

This function should also throw a runtime error if the returned index is -1.


Updating RecordManager::DisplayAllOpenFiles

This function won’t cause any exceptions. Therefore, you should add the function specifier, noexcept, to the end of the signature - both in the declaration and the definition.


Updating RecordManager::FindAvailableFile

Add the noexcept function specifier.


Updating RecordManager::FindFilenameIndex

Add the noexcept function specifier.


Testing it out

Now when you run the program, it won’t flat out crash like before, but it will abort the program once the first exception is hit.
[...]
END OF TEST 2

-------------------------------------
TEST 3: Write to a file that isn't opened

Open files: 
terminate called after throwing an instance of 'std::runtime_error'
  what():  File Test2.txt is not open
Aborted
(Note: This is the example output for the program running in Linux, with the g++ compiler. The result text might look different in Visual Studio.)

Next, try/catch blocks will need to be added in our tests so that the program will complete its execution, even if exceptions are discovered.


 

Adding try/catch

When we are calling a function that may cause an exception, that’s when we should have a try/catch statement. The functions from RecordManager that may cause exceptions are:

  • OpenOutputFile
  • CloseOutputFile
  • WriteToFile

Test1 and Test2 themselves won’t cause any exceptions to be thrown.

A try/catch block will look like this:

try
{
    // Function that may have a "throw" in it...
    DoRiskyThing();
}
catch( EXCEPTION_TYPE& ex )
{
    // Display error message
    cout << "Error: " << ex.what() << endl;
    
    // Handle the exception!
    // This might mean cleaning up resources, logging the error,
    // rolling back the program state, or even telling the program to close.
    UndoRiskyness();
}

We could wrap each individual function in a try/catch, or wrap all three of them together. This is a design decision to make.

In general, it is considered good design to wrap your try/catch around the smallest possible amount of code, but it also doesn’t need to wrap just one line at a time. Since these three functions are related, we can wrap the three in a single try/catch block and it would be fairly clean.

Mainly, don’t wrap an entire function body in a try/catch - only a section working with “risky” areas.


Updating Test3, Test4, and Test5

For each of these tests, surround only the function calls that may return with an exception. Remember that any functions marked as noexcept will never throw an exception.

Once you’ve updated the entire program, the output should look like this:

-------------------------------------
TEST 1: Open one file and write to it

Open files: 
0. Test1.txt

END OF TEST 1

-------------------------------------
TEST 2: Open 5 files and write to them

Open files: 
0. Test2_A.txt
1. Test2_B.txt
2. Test2_C.txt
3. Test2_D.txt
4. Test2_E.txt

END OF TEST 2

-------------------------------------
TEST 3: Write to a file that isn't opened

Open files: 
Error: File Test2.txt is not open

END OF TEST 3

-------------------------------------
TEST 4: Close a file that isn't opened

Open files: 
Error: File Test3.txt is not open

END OF TEST 4

-------------------------------------
TEST 5: Try to open more than max # of files

Error: No available files
Open files: 
0. Test5_A.txt
1. Test5_B.txt
2. Test5_C.txt
3. Test5_D.txt
4. Test5_E.txt

END OF TEST 5

Process returned 0 (0x0)   execution time : 0.005 s
Press ENTER to continue.

 

Turn in

Turn in the main.cpp, RecordManager.hpp, and RecordManager.cpp files in Canvas.

Please ZIP these source files together, but DO NOT INCLUDE any visual studio files or extra stuff.