CS247 Lecture 12
Last Time: Pure virtual, polymorphic arrays, polymorphic Big 5 This Time: Polymorphic assignment problem finished, exceptions
Continuing our implementation of AbstractBook to fix both the mixed and partial assignment problem.
To make this abstract, we need a pure virtual method. If no other methods make sense to be pure virtual, we can always use destructor.
How does this fix the two problem?
- Mixed assignment:
operator=
is non-virtual and the implicitly provided copy assignment operator only acceptsText
. - Partial assignment problem:
Note: only works since AbstractBook
is an abstract class. If we set Book::operator=
to be protected
then we couldn’t assign books to one another. ??????????? (What does this sentence mean)
Consider Text’s destructor. Implicitly, the following happens
- Destructor body runs (empty)
- Object fields we destructed in reverse decl. order
- Superclass destructor runs
- Space is reclaimed
Because in step 3, Text
’s destructor calls AbstractBook’s destructor, we have a problem: we’ve called a method with no implementation.
Solution: give it an implementation:
Nnote
Pure virtual methods don’t have to be implemented, but they still can be. They require an implementation if they will be called.
AbstractBook
is still abstract, only subclasses of classes which define a pure virtual method may be concrete. (Doesn’t mean that the destructor that is pure virtual has an implementation will make it concrete. It is still an abstract class)
One possible recommendation: If you care about assignment, consider making your superclasses abstract. (What does care about assignment mean??)
Error Handling
Example from STL vector
Vectors: dynamically allocated resizing arrays. Handles memory management so we don’t screw it up.
Unfortunately, we can’t idiot proof everything.
- Ross complains about cs138
v.at()
andv[i]
being checked or unchecked.
How to handle errors:
Option 1: Sentinel values. Reserve some values, -1
, INT_MIN
to signal errors:
- Problem: reduces what we can return, can’t return
-1
in a regular scenario. Not clear for a general type what values we should pick as sentinels.
Option 2: global variables. Create some global variable that it set when an error occurs (in C, int errno
, which can be queried for errors with standard functions).
- Also not ideal: Limited to the number of errors, might be overwritten.
Option 3: Bundle in a struct
:
- Best so far, but still not ideal. Wrap our return types in this
struct
, all return types are larger than needed. Awkward to access data field.
These are all approaches that C
users end up using. C++
however has a language feature for dealing with errors: exceptions.
v.at(100)
fetches v[100]
if the value exists, otherwise throws an exception.
r
is just an object, class type isstd::out_of_range
(included in<std except>
)- The
.what()
method returns a string describing the exception.
Force the programmer to deal with the error because the control flow jumps. Vector knows the error happened but not how to fix it. We know how to fix it, but not how the error occurred non locality error handling.
To raise an exception ourself, we use the “throw” keyword. We can throw any value, but keep in mind that <stdexcept>
has objects for common scenarios like out_of_range
, logic_error
, invalid_argument
.
When an exception is raised, control flow steps. (control flow is interrupted). Program starts to search through the stack upwards (reverse order) looking for a handler for this type of exception “Stack unwinding”. As the control flow moves up the call stack, destructors are run for objects stored on the stack during the process of stack unwinding. (Ensures that any resources held by those objects are properly released and cleaned up). If a handler is found, we jump to that point. If no handler is found, program crashes.
call stack
A call stack is a data structure that keeps track of function calls and their respective contexts.
Summary
When an exception is thrown in C++, the control flow jumps to find an appropriate handler for the exception. If a handler is found, the program continues execution from that point. If no handler is found, the program crashes. Stack unwinding takes place during the search for a handler, executing destructors to clean up objects on the stack. Exception handling allows programmers to deal with errors and exceptional conditions in a structured and controlled manner.
Example:
Main calls h
, h
calls q
, q
calls f
, throws, stack unwinding through q
, h
, jump to catch block in main.
Multiple errors may be handled via multiple catch blocks:
One handler can also deal with part of an error, re-throw the exception to allow someone else to deal with it.
The design of having multiple handlers allows for different parts of the code to handle specific types of exceptions. In this scenario, the DataStructureHandler()
function deals with errors related to the DataStructure
object, while the main()
function handles errors related to invalid user input. By re-throwing exceptions, the program can delegate the responsibility of handling specific exceptions to different parts of the code.
Next: CS247 Lecture 13