Jan 16, 2013

[C++][NOTE] Exception Safe reminder

Visit exceptionsafecode.com for more details.
This is just my excerpt from Mr.Jon Kalb's talk/ppt




Copies can fail

Destructor NEVER throw

• Use RAII.
• Responsibility Acquisition Is Initialization.
• Every responsibility is an object
• One responsibility per object
• Keep your resources on a short leash to not go leaking wherever they want.
• Avoid calling new.
• Never incur a responsibility as part of an expression that can throw.
• No more than one new in any statement
• allocate heap mem for initilizing pointer inside the constructor body, not throw initilizing list.
• placement new/ (placement) destructor


Cleanup Code:
• Don’t write cleanup code that isn’t being called by a destructor.
• Destructors must cleanup all of an object’s outstanding responsibilities.
• Be suspicious of cleanup code not called by a destructor.
• All cleanup code is called from a destructor.
• An object with such a destructor must be put on the stack as soon as calling the cleanup code become a responsibility.


SWAP:
Our custom swaps can be No Throw
• Don’t use non-swapping base/member classes
• Don’t use const or reference data members
• These are not swappable
• Support swapperator for value classes.
• Must deliver the No-Throw guarantee.


Calling swap in a template
template…
{
 …
 using std::swap;// shc: the reason for this : two-phase lookup and ADL
 swap(a, b);
 …
}


Calling swap in a template (alternative)
#include "boost/swap.hpp"
boost::swap(a, b);


Guide Line:
• Do not use dynamic exception specifications.
• Do use noexcept.
• Cleanup
• Destructors are noexcept by default
• Move/swap
• !!Implementing the Strong Guarantee
• !!Deferring the commit until success is guaranteed
• Use “Critical Lines” for Strong Guarantees.


//c++03
struct ResourceOwner
{
 ResourceOwner& operator=(ResourceOwner const&rhs)
 {
 Resource temp(*rhs.mResource);
//----------------The Critical Line
 temp.swap(*mResource);
 return *this;
 }

private:
 Resource* mResource;
};


//c++03
struct ResourceOwner
{
void swap(ResourceOwner&); // No Throw
ResourceOwner& operator=(ResourceOwner rhs)
{
 swap(rhs);
 return *this;
 }

private:
 Resource* mResource;
};


//c++11
struct ResourceOwner
{
 void swap(ResourceOwner&) noexcept;
 ResourceOwner& operator=(ResourceOwner const& rhs);
 ResourceOwner& operator=(ResourceOwner&& rhs) noexcept;

private:
 Resource* mResource;
};


//c++11
struct ResourceOwner
{
void swap(ResourceOwner&) noexcept;
 ResourceOwner& operator=(ResourceOwner const&rhs)
 {
 ResourceOwner temp(rhs);
 swap(temp);
 return *this;
 }
private:
 Resource* mResource;
};


void FunctionWithStrongGuarantee()
{
// Code That Can Fail
ObjectsThatNeedToBeModified.MakeCopies(OriginalObjects);
ObjectsThatNeedToBeModified.Modify();

//-------------------The Critical Line
// Code That Cannot Fail (Has a No-Throw Guarantee)
ObjectsThatNeedToBeModified.swap(OriginalObjects);
}



Where to try/catch
• Switch
Anywhere that we need to switch our method of error reporting.

• Anywhere that we support the No-Throw Guarantee

• Destructors & Cleanup

• Swapperator & Moves

• C-API

• OS Callbacks

• UI Reporting

• Converting to other exception types

• Threads



• Strategy
Anywhere that we have a way of dealing with an error such as an alternative or fallback method.

• Some success
Anywhere that partial failure is acceptable.

Know where to catch.
• Switch
• Strategy
• Some Success


Guide Line:
Make interfaces easy to use correctly and hard to use incorrectly.
• Prefer Exceptions to Error Codes
• Throwing exceptions should be mostly about resource availability
• When possible, provide defined behavior and/or use strong pre-conditions instead of failure cases
Don't use exceptions for general flow control
• Exceptions getting thrown during normal execution is usually an indication of a design flaw


Exception-Safety Guidelines:
• Throw by value. Catch by reference.
• No dynamic exception specifications. Use noexcept.
• Destructors that throw are evil.
• Use RAII. (Every responsibility is an object. One per.)
• All cleanup code called from a destructor
• Support swapperator (With No-Throw Guarantee)
• Draw “Critical Lines” for the Strong Guarantee
• Know where to catch (Switch/Strategy/Some Success)
• Prefer exceptions to error codes.


Implementation Techniques:
on_scope_exit:

• Creating a struct just to do one-off cleanup can be tedious.

• That is why we have on_scope_exit.


Lippincott Functions:

• A technique for factoring exception handling code.

• Example in The C++ Standard Library 2nd Ed. by Nicolai M. Josuttis page 50


Legacy Code:

• Transitioning from pre-exception/exceptionunsafe legacy code

• Does not handle code path disruption gracefully

• Sean Parent’s Iron Law of Legacy Refactoring

• Existing contracts cannot be broken!




Guide Line:
1. All new code is written to be exception safe
2. Any new interfaces are free to throw an exception
3. When working on existing code, the interface to that code must be followed - if it wasn't throwing exceptions before, it can't start now


Refactoring Steps:
• Consider implementing a parallel call and re-implementing the old in terms of the new
• Implement a parallel call following exception safety guidelines
• Legacy call now calls new function wrapped in try/catch (...)
Legacy API unchanged / doesn’t throw

• New code can always safely call throwing code
• Retire wrapper functions as appropriate


What does Exception-Safe Code look like?
There is no “try.” — Yoda



No comments:

Post a Comment

Note: Only a member of this blog may post a comment.