cs11 – c++ dgc - california institute of...

Post on 16-Mar-2018

214 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

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