{ "cells": [ { "cell_type": "code", "execution_count": 2, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "#include \n", "#include \n", "#include // stdexcept header contains runtime_error\n", "using namespace std;" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Exceptions" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Intro\n", "\n", "- An **exception** is an indication of a problem that occurs during a program's execution.\n", "- Exception handling enables you to create applications that can handle exceptions and perform appropriate cleanup when an exception that cannot or should not be handled occurs.\n", "- In many cases, this allows a program to continue executing as if no problem had been encountered.\n", "- The features presented in this chapter enable you to write **robust** and **fault-tolerant** programs that can deal with problems that may arise and continue executing or terminate gracefully." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Software Engineering\n", "\n", "- Exceptions provide a way to react to exceptional circumstances (like runtime errors) in programs by transferring control to special functions called **handlers**.\n", "- Exception handling provides a standard mechanism for processing errors.\n", " - This is especially important when working on a large project.\n", "- Without exception handling, it's common for a function to calculate and return a value on success or return an error indicator on failure.\n", " - A common problem with this architecture is using the return value in a subsequent calculation without first checking whether the value is the error indicator.\n", " - Exception handling eliminates this problem. " ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Example: Memory Allocation Exception \n", "\n", "- Exception thrown on failure allocating memory\n", " - Type of the exceptions thrown by the standard definitions of operator `new` and operator `new[]` when they fail to allocate the requested storage space.\n" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "ename": "Standard Exception", "evalue": "std::bad_alloc", "output_type": "error", "traceback": [ "Standard Exception: std::bad_alloc" ] } ], "source": [ "{\n", " int* myarray= new int[1000000000000];\n", " cout << \"Done!\";\n", "}" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Exception Handling Workflow\n", "\n", "- To catch exceptions, a portion of code is placed under exception inspection.\n", "- This is done by enclosing that portion of code in a **`try` block**.\n", " - When an exceptional circumstance arises within that block, an exception is thrown that transfers the control to the exception handler. \n", " - If no exception is thrown, the code continues normally and all handlers are ignored." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Exception Handling Workflow (cont.)\n", "\n", "- The exception handler is declared with the **`catch`** keyword immediately after the closing brace of the **`try` block**.\n", " - The syntax for catch is similar to a regular function with one parameter.\n", " - The type of this parameter is very important, since the type of the argument passed by the throw expression is checked against it, and only in the case they match, the exception is caught by that handler. " ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "This is an exception: std::bad_alloc\n", "Done!" ] } ], "source": [ "try\n", "{\n", " int* myarray= new int[1000000000000];\n", "}\n", "catch (bad_alloc& e)\n", "{\n", " cout << \"This is an exception: \" << e.what() << '\\n';\n", "}\n", "cout << \"Done!\";" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Example: Exception Handling Workflow\n", "\n", "- Consider a common arithmetic problem - **division by zero**.\n", " - Division by zero using integer arithmetic typically causes a program to terminate prematurely.\n", " - In floating-point arithmetic, many C++ implementations allow division by zero, in which case positive or negative infinity is displayed as **inf** or **-inf**, respectively.\n", "- Typically, a program would simply test for division by zero before attempting the calculation\n", " - Use exceptions here to present the flow of control when a program executes successfully and when an exception occurs." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Example: Division By Zero Exception\n", "\n", "- Define a function **`quotient`** that receives two integers entered by the user and divides its first `int` parameter by its second `int` parameter.\n", " - Before performing the division, the function casts the first `int` parameter's value to type `double`.\n", " - Then, the second `int` parameter's value is promoted to type `double` for the calculation.\n", " - So function quotient actually performs the division using two `double` values and returns a `double` result.\n", " \n", "- For the purpose of this example we **treat any attempt to divide by zero as an error**.\n", " - If the second parameter is zero, the function uses an exception to indicate to the caller that a problem occurred. " ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Example: Division By Zero Exception (cont.)\n", "\n", "- For the purpose of this example we **treat any attempt to divide by zero as an error**.\n", " - Thus, function `quotient` tests its second parameter to ensure that it isn't zero before allowing the division to proceed.\n", " - If the second parameter is *zero*, the function uses an **exception** to indicate to the caller that a problem occurred.\n", "- The caller can then process the exception and allow the user to type two new values before calling function quotient again.\n", " - In this way, the program can continue to execute even after an improper value is entered, thus making the program more robust. " ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "// DivideByZeroException objects should be thrown by functions\n", "// upon detecting division-by-zero exceptions\n", "class DivideByZeroException : public std::runtime_error {\n", "public:\n", " // constructor specifies default error message\n", " DivideByZeroException() : runtime_error{\"attempted to divide by zero\"} {}\n", "};" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "// perform division and throw DivideByZeroException object if divide-by-zero exception occurs\n", "double quotient(int numerator, int denominator) {\n", " // throw DivideByZeroException if trying to divide by zero\n", " if (denominator == 0) {\n", " throw DivideByZeroException{}; // terminate function\n", " }\n", " // return division result\n", " return static_cast(numerator) / denominator;\n", "}" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Exception occurred: attempted to divide by zero\n", "Done!" ] } ], "source": [ "{\n", " int number1 = 1, number2 = 0;\n", " try {\n", " double result = quotient(number1, number2);\n", " cout << \"The quotient is: \" << result << endl;\n", " }\n", " catch (const DivideByZeroException& divideByZeroException) {\n", " cout << \"Exception occurred: \" << divideByZeroException.what() << endl;\n", " }\n", " cout << \"Done!\";\n", "}" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Exception Class\n", "\n", "- A typical exception class that derives from the `runtime_error` class *defines only a constructor* that passes an error-message string to the base-class runtime_error constructor.\n", "- Every exception class that derives directly or indirectly from exception contains the *virtual function* `what`, which returns an exception object's error message.\n", " - You are not required to derive a custom exception class, such as `DivideByZeroException`, from the standard exception classes provided by C++.\n", " - Doing so allows you to use the virtual function `what` to obtain an appropriate error message and also allows you to polymorphically process the exceptions by catching a reference to the base-class type." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## `Catch` Handler\n", "\n", "- At least one `catch` handler **must immediately follow** each `try` block.\n", "- An exception parameter should always be declared as a **reference** to the type of exception the catch handler can process\n", " - this prevents copying the exception object when it's caught and allows a catch handler to properly catch derived-class exceptions as well.\n", "- The catch handler that executes is the **first** one whose type **matches the type of the exception** that occurred\n", "- If an exception parameter includes an optional parameter name, the catch handler can use that parameter name to interact with the caught exception in the body of the catch handler" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Termination Model of Exception Handling\n", "\n", "1. If an exception occurs as the result of a statement in a `try` block, the `try` block expires (i.e., terminates immediately).\n", "2. Next, the program searches for the first `catch` handler that can process the type of exception that occurred.\n", "3. The program locates the matching catch by comparing the thrown exception's type to each `catch`'s exception-parameter type until the program finds a match.\n", "4. A match occurs if the types are identical or if the thrown exception's type is a derived class of the exception-parameter type.\n", "5. When a match occurs, the code in the matching catch handler executes." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Termination Model of Exception Handling (cont.)\n", "\n", "6. When a `catch` handler finishes processing by reaching its closing right brace (}), the exception is considered handled and the local variables defined within the catch handler (including the catch parameter) go out of scope.\n", "7. Program control does not return to the point at which the exception occurred (known as the throw point), because the `try` block has expired.\n", "8. Rather, control resumes with the **first statement after the last `catch` handler** following the `try` block." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Termination Model of Exception Handling (cont.)\n", "\n", "- This is known as the **termination model of exception handling**.\n", " - As with any other block of code, when a try block terminates, local variables defined in the block go out of scope.\n", "- If the `try` block completes its execution successfully (i.e., no exceptions occur in the try block), then the program ignores the `catch` handlers and program control continues with the first statement after the last catch following that try block.\n", "- If an *exception that occurs* in a `try` block has no matching `catch` handler, or if an exception occurs in a statement that is not in a try block, the function that contains the statement terminates immediately, and the program attempts to locate an enclosing `try` block in the calling function." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Rethrowing An Exception\n", "\n", "- A function might use a resource - like a file - and might want to release the resource if an exception occurs.\n", "- An exception handler, upon receiving an exception, can release the resource then notify its caller that an exception occurred by rethrowing the exception via the statement\n", "```c++\n", "throw;\n", "```\n", "- Regardless of whether a handler can process an exception, the handler can **rethrow the exception** for further processing outside the handler.\n", "- The next enclosing try block detects the rethrown exception, which a catch handler listed after that enclosing try block attempts to handle." ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "void throwException() {\n", " // throw exception and catch it immediately\n", " try {\n", " cout << \" Function `throwException` throws an exception\\n\";\n", " throw exception{}; // generate exception\n", " }\n", " catch (const exception&) { // handle exception\n", " cout << \" Exception handled in function `throwException`\"\n", " << \"\\n Function `throwException` rethrows exception\";\n", " throw; // rethrow exception for further processing\n", " }\n", " cout << \"This should not print\\n\";\n", "}" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Invoke function throwException\n", " Function `throwException` throws an exception\n", " Exception handled in function `throwException`\n", " Function `throwException` rethrows exception\n", "\n", "Exception handled in the caller's scope\n", "Program control continues after catch\n" ] } ], "source": [ "{\n", " // throw exception\n", " try {\n", " cout << \"Invoke function throwException\\n\";\n", " throwException();\n", " cout << \"This should not print\\n\";\n", " }\n", " catch (const exception&) { // handle exception\n", " cout << \"\\n\\nException handled in the caller's scope\\n\";\n", " }\n", " cout << \"Program control continues after catch\\n\";\n", "}" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Stack Unwinding\n", "\n", "- When an exception is thrown but not caught in a particular scope, the function call stack is **\"unwound\"**, and an attempt is made to catch the exception in the next outer `try...catch` block.\n", "- **Unwinding the function call stack** means that the function in which the exception was not caught terminates, all local variables that have completed intitialization in that function are destroyed and control returns to the statement that originally invoked that function.\n", " - If a `try` block encloses that statement, an attempt is made to `catch` the exception.\n", " - If a `try` block **does not enclose** that statement, stack unwinding **occurs again**.\n", " - If no `catch` handler ever catches this exception, the program terminates." ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "// function2 invokes throwException\n", "void function2() {\n", " cout << \"`throwException` is called inside `function2`\" << endl;\n", " throwException(); // stack unwinding occurs, return control to function1\n", " cout << \"Done f2\";\n", "}" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "// function1 invokes function2\n", "void function1() {\n", " cout << \"`function2` is called inside `function1`\" << endl;\n", " function2(); // stack unwinding occurs, return control to main\n", " cout << \"Done f1\";\n", "}" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "`function1` is called\n", "`function2` is called inside `function1`\n", "`throwException` is called inside `function2`\n", " Function `throwException` throws an exception\n", " Exception handled in function `throwException`\n", " Function `throwException` rethrows exception\n", "\n", "Exception occurred: std::exception\n", "Exception handled\n" ] } ], "source": [ "{\n", " // invoke function1\n", " try {\n", " cout << \"`function1` is called\" << endl;\n", " function1(); // call function1 which throws runtime_error\n", " }\n", " catch (const exception& error) { // handle runtime error\n", " cout << \"\\n\\nException occurred: \" << error.what() << endl;\n", " cout << \"Exception handled\" << endl;\n", " }\n", "}" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Constructors and Exception Handling\n", "\n", "- What happens when an error is detected in a **constructor**?\n", " - Because the constructor **cannot return a value** to indicate an error, we must choose an alternative means of indicating that the object has not been constructed properly.\n", "\n", "Error handling:\n", "1. Return the improperly constructed object and hope that anyone using it would make appropriate tests to determine that it's in an inconsistent state.\n", "2. Set some variable outside the constructor.\n", "3. Require the constructor to throw an exception that contains the error information, thus offering an opportunity for the program to handle the failure.\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Destructors and Exception Handling\n", "\n", "- If an exception occurs during object construction, destructors may be called:\n", " - If an exception is thrown before an object is fully constructed, destructors will be called for any member objects that have been constructed so far.\n", " - If an array of objects has been partially constructed when an exception occurs, only the destructors for the array’s constructed objects will be called.\n", "- Destructors are called for every automatic object constructed in a try block before an exception that occurred in that block is caught.\n", "- Stack unwinding is guaranteed to have been completed at the point that an exception handler begins executing.\n", "- If a destructor invoked as a result of stack unwinding throws an exception, the program terminates." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Error-Prevention Tip\n", "\n", "- Destructors should catch exceptions to prevent program termination.\n", "- Do not throw exceptions from the constructor of a global object or a static local object.Such exceptions cannot be caught, because they're constructed before `main` executes.\n", "- When an exception is thrown from the constructor for an object that's created in a new expression, the dynamically allocated memory for that object is released.\n", "- A constructor should throw an exception if a problem occurs while initializing an object. Before doing so, the constructor should release any memory that it dynamically allocated." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Standard Library Exception Classes\n", "\n", "![Exceptions](../img/Exceptions.jpg)" ] } ], "metadata": { "celltoolbar": "Slideshow", "kernelspec": { "display_name": "C++14", "language": "C++14", "name": "xcpp14" }, "language_info": { "codemirror_mode": "text/x-c++src", "file_extension": ".cpp", "mimetype": "text/x-c++src", "name": "c++", "version": "-std=c++14" } }, "nbformat": 4, "nbformat_minor": 2 }