cs11 – c++ dgc - california institute of...
Post on 16-Mar-2018
214 Views
Preview:
TRANSCRIPT
CS11 – C++ DGC
Spring 2006-2007Lecture 2
Today’s Topics
C++ Standard ExceptionsException propagation and cleanup“Resource Allocation Is Initialization” pattern
C++ Exceptions
Exceptions are nice for reporting many errorsCode throwing the exception can detect the problem, but doesn’t know how to handle it.Code that catches the exception knows what to do about the problem.With careful implementation of constructors and destructors, resources get cleaned up, too.
C++ Standard Library provides a number of standard exception classes
Simple Example
void calculate(float x) {if (x < 0)throw domain_error("x is negative");
// Do our calculation....
}
int main() {float x;cin >> x;try {calculate(x);
} catch (domain_error) {cout << "Caught a domain error!" << endl;
}}
What Happened?
void calculate(float x) {if (x < 0)throw domain_error("x is negative");
// Do our calculation....
}
int main() {float x;cin >> x;try {calculate(x);
} catch (domain_error) {cout << "Caught a domain error!" << endl;cout << de.what() << endl;
}}
C++ Standard Exceptions provide a what() function to
retrieve error details.
C++ Standard Exceptions
exception#include <exception>
logic_error#include <stdexcept>
runtime_error#include <stdexcept>
bad_alloc#include <new>
bad_exception#include <exception>
bad_cast#include <typeinfo>
bad_typeid#include <typeinfo>
ios_base::failure#include <ios>
length_error#include <stdexcept>
domain_error#include <stdexcept>
out_of_range#include <stdexcept>
invalid_argument#include <stdexcept>
range_error#include <stdexcept>
overflow_error#include <stdexcept>
underflow_error#include <stdexcept>
Thrown by theC++ language
Standard Exception Hierarchy
Two major kinds of standard exceptionslogic_error
Intended for “preventable” errors, a.k.a. bugsInvalid function arguments, violated invariants, etc.A potential alternative to using assert()Steer clear of these for reporting runtime errors!
runtime_error“All other errors.”Errors that can only be caught as the program executes
Using the Standard Exceptions
These are available for use in your programs.Use them as-is, subclass them, or ignore them and make your own!You will probably have to handle them, at least…
“Some people view this as a useful framework for all errors and exceptions; I don’t.”
- Bjarne StroustrupThe C++ Programming Language §14.10
Local Variables and Destructors
Normally, destructors are called when variables go out of scope
void myFunction() {Class1 var1;Class2 var2("out.txt");
var1.doStuff(var2);}
Compiler inserts destructor calls into the appropriate placesYour code doesn’t manually call destructors, ever.
var2.~Class2();var1.~Class1();
Compiler adds:
Destructors and Exceptions
void myFunction() {Class1 var1;Class2 var2("out.txt");
var1.doStuff(var2);
}
What happens if var2 constructor throws?Only var1 was constructed, so only var1 destructor gets called
var2.~Class2();var1.~Class1();
THROW!
propagateexception
cleanup(stack unwinding)
Destructors and Exceptions (2)
void myFunction() {Class1 var1;Class2 var2("out.txt");
var1.doStuff(var2);
}
What happens if var1 constructor throws?Nothing was constructed, so no destructors get called
var2.~Class2();var1.~Class1(); propagate
exception
no cleanup neededTHROW!
Destructors and Exceptions (3)
void myFunction() {Class1 var1;Class2 var2("out.txt");
var1.doStuff(var2);
}
What happens if var1.doStuff(var2) throws?Both var1 and var2 were constructed, so both destructors get called (in reverse order of construction)
var2.~Class2();var1.~Class1();
THROW!
propagateexception
cleanup(stack unwinding)
Classes and Exceptions
Similar model used when constructors throwclass Logger {LogConfig config;RotatingFile outputFile;
public:Logger(const string &configFile) {... // initialize logger
}...
};
What happens if the constructor body throws?The new Logger instance failed to be constructedconfig and outputFile have already been initialized, so their destructors are automatically called
THROW!
Classes and Exceptions (2)
Member initialization might also throwclass Logger {LogConfig config;RotatingFile outputFile;
public:Logger(const string &configFile) :config(configFile), outputFile(config)
{... // initialize logger
}...
};What happens if outputFile constructor throws?
The new Logger instance failed to be constructed (again)config was already initialized, so its destructor gets called
THROW!
Classes and Exceptions (3)
Another member constructor throwsclass Logger {LogConfig config;RotatingFile outputFile;
public:Logger(const string &configFile) :config(configFile), outputFile(config)
{... // initialize logger
}...
};What happens if config constructor throws?
The new Logger instance failed to be constructed (yet again)Nothing was initialized, so no member destructors are called
THROW!
Safe Dynamic-Resource Management
Problem:Dynamic allocation of resources, plus exception-handling, is a potentially dangerous mix!
Memory allocated with newOpening files, pipes, etc.Threads, mutexes, condition variables, semaphores, …
Just catching exceptions isn’t enough!Also need to release any resources that were allocated before exception was thrown.
Allocation and Exceptions
Example:Simulator::Simulator(SimConfig *pConf) {entityData = new Entity[pConf->maxEntities];playerData = new Player[pConf->maxPlayers];
}
What happens if second allocation throws bad_alloc?
Simple: entityData doesn’t get cleaned up
A Safer Constructor
Can fix the problem by doing this:Simulator::Simulator(SimConfig *pConf) :entityData(0), playerData(0)
{try {
entityData = new Entity[pConf->maxEntities];playerData = new Player[pConf->maxPlayers];
}catch (bad_alloc &ba) {
delete[] entityData;delete[] playerData;throw; // Don’t forget to propagate this!
}}
Not the prettiest code, but at least it’s safe.
Again and Again!
This pattern gets old fast:void initSimulation() {SimConfig *pConf = new SimConfig("sim.conf");Simulator *pSim = new Simulator(pConf);...
}
What if Simulator constructor throws?(sigh)
This approach to leak-free, exception-safe code is a pain!
Typical Resource Allocation Model
General form of the problem:void doStuff() {// acquire resource 1// ...// acquire resource N
// use the resources
// release resource N// ...// release resource 1
}
Resources usually released in opposite order of allocationHey, C++ constructors and destructors do this!
Local variables are created and destroyed this way
Exceptions and Local Variables
Example with objects:void doStuff() {Resource1 r1;
// This could throw exceptionsriskyBusiness(r1);
Resource2 r2;doSomethingElse(r1, r2);
}
Local variables are only destructed if they were constructed before an exception is thrown
If riskyBusiness throws, only r1 needs to be cleaned upLocal variables are destructed in opposite order of construction
r2 is destructed (if necessary), then r1 is destructed
Easier Leak-Proofing Approach!
Make a wrapper class for managing a dynamic resource
Constructor allocates the dynamic resourceDestructor frees the resourceUse the wrapper class for local variables
(Otherwise, you’re back to the old problems again…)
“Clean up” exception-handlers become unnecessary
When exception is thrown, C++ will call wrapper-class destructor automatically, since it’s local.
“Resource Allocation Is Initialization”
This pattern is called “Resource allocation is initialization.”
A local variable’s constructor immediately assumes ownership of the dynamic resourceC++ will call the destructor at the Right Time.
Typically realized as “smart pointers”They follow this model for heap-allocated memory
This can be applied to any dynamic resourceFiles! Semaphores! Mutexes! …
This Week’s Lab
Implement “Resource Allocation Is Initialization” pattern for POSIX threading constructs
A wrapper class to manage a POSIX threading primitiveConstructor can allocate the primitiveDestructor can clean up the primitiveWrapper class automates cleanup operations when exceptions occurCan also indicate errors with exceptions
POSIX Mutex Wrapper
Mutex operations:Allocated with pthread_mutex_init()Destroyed with pthread_mutex_destroy()Lock, unlock, try-lock, etc.
POSIX functions can return error codesCreate a Mutex class that wraps a POSIX mutex
Constructor initializes mutex variableDestructor cleans up mutex
Mutex Errors
If mutex can’t be created:Wrapper class constructor can throw an exception
When mutex lock/unlock operation fails:Throw an exception to indicate the issue
When mutex destructor operation fails:Just log an error to cerr; don’t throw!
RAII Pattern:Constructors should throw when construction failsDestructors should never ever throw!!
Destructors and Exceptions
Destructors should never ever throw.Breaks process of cleaning up collections of objects
Example:Widget *batch = new Widget[20];Widget default constructor called on each widget in the batch
What does this do?delete[] batch;Widget destructor called on each widget in the batchThen, memory for array is reclaimed
What if batch[3] destructor throws an exception?Rest of objects are leaked!
More Mutex Mangling
Create a mutex variable:Mutex m;
Can lock and unlock it, etc.Internal POSIX pthread_mutex_t is cleaned up automatically (Yay RAII)
What does this mean?Mutex m2(m);
Or this?Mutex m3;m3 = m;
Copying Resources
Basic question:What are the semantics of copying a resource?If multiple objects share a particular resource, which one should free it?
Often, easiest solution is to disallow copyingMutex is a good example
Approach:Declare copy-constructor and assignment operator to be privateImplementation can assert(false) too!
Uncopyable Widgets
To disallow copying and assignment:class Widget {double weight;
// Disallow copy construction.Widget(const Widget &) { assert(false); }
// Disallow assignment. Return void to simplify.void operator=(const Widget &) { assert(false); }
public:Widget(double w) : weight(w) { ... }...
};
Document these situations in your code!
Another Useful Trick
Locks on mutexes are also resourcesWould like locks to be released automatically when an exception is thrown
Example:void doStuff() {
// Lock mutex.m.lock();// This could throw!riskyBusiness();// Finally, unlock the mutex.m.unlock();
}If function throws, mutex will remain locked!
Lock Objects
Create a Lock class that manages a lock on a Mutex objectLock constructor acquires a lock on the mutexLock destructor releases the lockIf error during lock operation, Lock constructor should throwLock destructor should never throw
Need to catch Mutex::unlock() exceptions…The Lock class has no other member functions!
Lock should disallow copying too
Exception-Safe Locking
Now our locking code is exception-safe:void doStuff() {
// Lock mutex.Lock lock(m);// This could throw!riskyBusiness();
}
If function throws, mutex is automatically unlockedOur code is slightly simpler, too
Lock Blocks
Can use blocks to temporarily acquire locks:void doMoreStuff() {{Lock lock(m);riskyBusiness1();
}...{Lock lock(m);riskyBusiness2();
}}
Scope of each lock variable is inside blockAutomatically released at end of blockStill exception-safe, like before
Next Time
No class next weekResume class in two weeks
Next time:Signaling between threadsBuilding larger-scale multi-threading components
top related