Exception-Safe Code - Jon Kalb - CppCon 2014
Exception-Safe Code - Jon Kalb - CppCon 2014
Website
http://exceptionsafecode.com!
!
• Bibliography!
• Video!
• Comments
2
Contact
• Email!
[email protected]!
• Follow!
@_JonKalb!
• Résumé!
[email protected]
Dedication
4-1
Dedication
4-2
The Promise
• Easier to Read!
Easier to Understand and
Maintain!
• Easier to Write!
• No time penalty!
• 100% Robust
5
! !
C++ 2003 C++ 2011
A Word on C++11
• I will cover both C++ 2003 and C++ 2011!
• Solid on classic C++!
• Some things still to learn about C++11!
• No fundamental change in exception-
safety!
Session Preview
• The problem!
• Solutions that don’t use exceptions!
• Problems with exceptions as a solution!
• How not to write Exception-Safe code!
• Exception-Safe coding guidelines!
• Implementation techniques
7
What’s the Problem?
8-1
8-2
Application Logic!
!
!
!
!
!
!
!
Low Level Implementation
9-1
Application Logic!
!
Layer of Code!
!
Layer of Code!
!
Layer of Code!
!
...!
!
Layer of Code!
!
Layer of Code
!
Low Level Implementation
9-2
Application Logic!
!
Layer of Code!
!
Layer of Code!
!
Layer of Code!
!
...!
!
Layer of Code!
! Oops
Layer of Code
!
Low Level Implementation
9-3
9-4
Application Logic! Don’t
worry! I’ll
! handle it.
Layer of Code!
!
Layer of Code!
!
Layer of Code!
!
...!
!
Layer of Code!
! Oops
Layer of Code
!
Low Level Implementation
9-5
Solutions without
Exceptions
• Addressing the problem without exceptions!
• Error flagging!
• Return codes
10
10
Error Flagging
• errno!
• “GetError” function
11
11
Error Flagging
errno = 0;!
old_nice = getpriority(PRIO_PROCESS, 0);!
/* check errno */!
if (errno)!
{!
/* handle error */!
}
12
12
Problems with the
Error Flagging Approach
13
13
Return Codes
• Return values are error/status codes!
• (Almost) every API returns a code!
• Usually int or long!
• Known set of error/status values!
• Error codes relayed up the call chain
14
14
Problems with the
Return Code Approach
• Errors can be ignored
• Are ignored by default
• Ifreturning
a single call “breaks the chain” by not
an error, errors cases are lost
• Code is tedious to read and write
• Exception based coding addresses both of
these issues…
15
15-1
15-2
The Dark Side
Broken error handling leads to bad states,
bad states lead to bugs,
bugs lead to suffering. !
— Yoda
16
16
17
17
T& T::operator=(T const& x)!
{!
! if (this != &x)!
! {!
! ! this->~T(); // destroy in place!
! ! new (this) T(x); // construct in place!
! }!
! return *this;!
}
18
18
19
19
The Dark Side
• Implementation issues are behind us!
• Today’s compilers: !
• Reliable, Performant, and Portable!
• What causes concerns today?
20
20
21
21
“”
“Counter-intuitively, the hard part of coding
exceptions is not the explicit throws and catches.
The really hard part of using exceptions is to write
all the intervening code in such a way that an
arbitrary exception can propagate from its throw
site to its handler, arriving safely and without
damaging other parts of the program along the
way.”!
– Tom Cargill
22
22
Counter-intuitively,!
this is true of any error handling system.
23
23
Cargill’s Article
24
24
Cargill’s Stumper
25
25
Standard’s Solution
!
template <class T> T& stack<T>::top(); !
!
template <class T> void stack<T>::pop();!
26
26
Cargill’s Article
27
27
Cargill’s Conclusions
28
28
Cargill’s Conclusions
We don’t
know how to be
exception-safe.
(1994)
29
29-1
Cargill’s Conclusions
We don’t
know how to be
exception-safe.
(1994)
Sure we do!
(1996)
29
29-2
Abrahams’ Conclusions
30
30
Joel on Software
31
31
Joel on Software
dosomething();!
cleanup();
32
32-1
Joel on Software
dosomething();!
cleanup();
32
32-2
Joel on Software
dosomething();!
cleanup();
33
33-1
Joel on Software
dosomething();!
cleanup();
33
33-2
First Steps
• Carefully check return values/error codes to
detect and correct problems.!
• Identify functions that can throw and think
about what to do when they fail!
• Use exception specifications so the compiler
can help create safe code.!
• Use try/catch blocks to control code flow
34
34
The Hard Way
• Carefully check return values/error codes to
detect and correct problems.!
• Identify functions that can throw and think
about what to do when they fail!
• Use exception specifications so the compiler
can help create safe code.!
• Use try/catch blocks to control code flow
35
35
36
36-1
The Wrong Way
• Carefully check return values/error codes to
detect and correct problems.
• Identify functions that can throw and think
about what to do when they fail
• Use exception specifications so the compiler
can help create safe code.
• Use try/catch blocks to control code flow
“You must unlearn what you have learned.”!
— Yoda
36
36-2
• Think structurally!
• Maintain invariants
37
37
Exception-Safe!
• Guidelines for code that is Exception-Safe!
• Few enough to fit on one slide!
• Hard requirements!
• Sound advice
38
38
Exception-Safety
Guarantees (Abrahams)
• Basic!
• invariants of the component are
preserved, and no resources are leaked!
• Strong!
• if an exception is thrown there are no
effects!
• No-Throw!
• operation will not emit an exception
39
39
Exception-Safety
Guarantees (Abrahams)
• Basic!
• invariants of the component are
preserved, and no resources are leaked!
• Strong!
• Yoda:!
“Do or do not.”
• No-Throw!
• operation will not emit an exception
40
40
Exception-Safety
Assumptions
• Basic guarantee!
• Cannot create robust code using
functions that don’t provide at least the
Basic guarantee – fixing this is priority
zero!
• All code throws unless we know
otherwise!
• We are okay with this
41
41
!
C++ 2011
Exception-Safety
Guarantees (Abrahams)
• No-Throw Required!
• Cleanup (destructors)!
• swap()!
• move operations (C++11)
42
42
Exception-Safety
Guarantees (Abrahams)
• Caller’s Point-of-View!
• The No-Throw Required functions are the
only functions that we need to be stronger
than Basic.!
• We assume all other code throws unless we
know otherwise. And we are okay with that.!
• This is a surprise to some!
43
43
Exception-Safety
Guarantees (Abrahams)
• Implementor’s Point-of-View!
• Always provide at least the Basic guarantee!
• Always provide No-Throw where Required!
• Document any stronger guarantees!
• Provide the Strong guarantee when it is
“natural”
44
44
Exception-Safety
Guarantees (Abrahams)
• What does it mean for the Strong guarantee
to be “natural?”
template <typename T> struct vector!
{!
...!
void push_back(T const&);!
...!
};
45
45
Exception-Safety
Guarantees (Abrahams)
• Case where size < capacity
46
Exception-Safety
Guarantees (Abrahams)
• Case where size == capacity
void push_back(T const&t)!
// allocate new buffer in temp ptr!
// copy existing items into new buffer!
// set new capacity!
new(&temp_buffer[size]) T(t);!
swap(temp_buffer, buffer);!
delete temp_buffer;!
++size;!
... 47
47
Exception-Safety
Guarantees (Abrahams)
48
48
Exception-Safety
Guarantees (Abrahams)
• When don’t you give the strong guarantee!
• Consider vector<>::insert()!
• Strong guarantee would require copying
and inserting into the copy!
• The Standard does not promise the Strong
guarantee
49
49
Mechanics
50
50-1
Mechanics
50
50-2
Error Detection
{!
/* A runtime error is detected. */!
ObjectType object;!
throw object;!
}!
Is object thrown?!
Can we throw a pointer?!
Can we throw a reference?
51
51
Error Detection
{!
std::string s("This is a local string.");!
throw ObjectType(constructor parameters);!
}
52
52
Mechanics
53
53
! try!
! {!
code_that_might_throw();!
! }!
! catch (A a) <== works like a function argument!
! {!
! ! error_handling_code_that_can_use_a(a);!
! }!
catch (...) <== “catch all” handler!
{!
! ! more_generic_error_handling_code();!
! }!
more_code();
54
54
! ...
! catch (A a)
! {
! ! ...
55
55-1
! ...
! catch (A a)
! {
! ! ...
!
• Issues with catching by value!
• Slicing!
• Copying (might throw)
55
55-2
! ...!
catch (A& a)!
{!
! ! a.mutating_member();!
! ! throw;!
! }
56
56
! try!
! {!
! ! throw A();!
! }
57
57-1
! try!
! {!
! ! throw A();!
! }
! catch (B) {}! ! // if B is a public base class of A!
! catch (B&) {}!
! catch (B const&) {}!
! catch (B volatile&) {}!
! catch (B const volatile&) {}!
! catch (A) {}!
! catch (A&) {}!
! catch (A const&) {}!
! catch (A volatile&) {}!
! catch (A const volatile&) {}!
! catch (void*) {}! // if A is a pointer!
! catch (…) {}
57
57-2
Guideline
• Throw by value.!
• Catch by reference.
58
58
Performance Cost of
try/catch
• No throw — no cost.
• In the throw case…
59
59-1
Performance Cost of
try/catch
• No throw — no cost.
• In the throw case…
• Don’t know. Don’t care.
59
59-2
!
Function
void F(int a)!
Try Blocks
! {!
!try!
{!
int b;!
! ...!
}!
catch (std::exception const& ex)!
{!
... // Can reference a, but not b!
... // Can throw, return, or end!
}!
}
60
60
61
61
Function Try Blocks
62
62
63
Function Try Blocks
64
64-1
64
64-2
Function Try Blocks
64
64-3
!
C++ 2011
Mechanics
65
65
!
C++ 2011
C++11 Supported
Scenarios
66
66
!
C++ 2011
Moving Exceptions
Between Threads
67
67
!
C++ 2011
Moving Exceptions
Between Threads
Capturing is easy!
<exception> declares:!
!
exception_ptr current_exception() noexcept;
68
68
!
C++ 2011
Moving Exceptions
Between Threads
• std::exception_ptr is copyable!
• The exception exists as long as any
std::exception_ptr using to it does!
• Can be copied between thread like any
other data
69
69
!
C++ 2011
Moving Exceptions
Between Threads
std::exception_ptr ex(nullptr);!
try {!
...!
}!
catch(...) {!
ex = std::current_exception();!
...!
}!
if (ex) {!
...
70
70
!
C++ 2011
Moving Exceptions
Between Threads
Re-throwing is easy!
<exception> declares:!
!
[[noreturn]] void rethrow_exception(exception_ptr p);!
71
71
!
C++ 2011
Moving Exceptions
Between Threads
A related scenario!
std::future<int> f = std::async(Func);!
72
72
!
C++ 2011
Nesting Exceptions
73
!
C++ 2011
Nesting Exceptions
Nesting the current exception is easy!
<exception> declares:!
!
class nested_exception;!
!
Constructor implicitly calls current_exception()
and holds the result.
74
74
!
C++ 2011
Nesting Exceptions
Throwing a new exception with the nested is easy!
<exception> declares:!
!
[[noreturn]] template <class T>!
void throw_with_nested(T&& t);!
!
Throws a type that is inherited from both T and!
std::nested_exception.
75
75
!
C++ 2011
Nesting Exceptions
try {!
try {!
...!
} catch(...) {!
std::throw_with_nested(MyException());!
}!
} catch (MyException&ex) {!
... handle ex!
... check if ex is a nested exception!
... extract the contained exception!
... throw the contained exception!
}
76
76
!
C++ 2011
Nesting Exceptions
77
77
!
C++ 2011
Nesting Exceptions
try {!
try {!
...!
} catch(...) {!
std::throw_with_nested(MyException());!
}!
} catch (MyException&ex) {!
... handle ex!
... check if ex is a nested exception!
... extract the contained exception!
... throw the contained exception!
}
78
78
!
C++ 2011
Nesting Exceptions
try {!
try {!
...!
} catch(...) {!
std::throw_with_nested(MyException());!
}!
} catch (MyException&ex) {!
... handle ex!
std::rethrow_if_nested(ex);!
}
79
79
Standard Handlers
• The “Terminate” Handler!
• Calls std::abort()!
80
Standard Handlers
81
81
!
C++ 2003
Exception Specifications
• Two flavors!
• C++ 2003!
• Exception Specifications!
• Now technically called
Dynamic Exception Specifications
82
82
!
C++ 2011
Exception Specifications
• Two flavors!
• C++ 2011!
• Introduces “noexcept” keyword!
83
83
!
C++ 2003
Dynamic Exception
Specifications
void F(); // may throw anything!
84
84
!
C++ 2003
Dynamic Exception
Specifications
• Not checked at compile time.!
85
85
!
C++ 2003
Guideline
86
86
!
C++ 2011
noexcept
87
87
!
C++ 2011
noexcept
• As a noexcept exception specification!
!
void F(); // may throw anything!
void G() noexcept(Boolean constexpr);!
void G() noexcept; // defaults to noexcept(true)!
Destructors are noexcept by default.
88
88
!
C++ 2011
noexcept
• As an operator!
static_assert(noexcept(2 + 3) , "");!
static_assert(not noexcept(throw 23) , "");!
89
89
!
C++ 2011
noexcept
• As an operator!
static_assert(noexcept(2 + 3) , "");!
static_assert(not noexcept(throw 23) , "");!
90
90
!
C++ 2011
noexcept
• As an operator!
static_assert(noexcept(2 + 3) , "");!
static_assert(not noexcept(throw 23) , "");!
91
91
!
C++ 2011
noexcept
• How will noexcept be used?!
• Operator form for no-throw based optimizations!
• move if no-throw, else do more expensive copying!
• Unconditional form for simple user-defined types!
struct Foo { Foo() noexcept {} };!
• Conditional form for templates with operator form!
template <typename T> struct Foo: T {!
Foo() noexcept( noexcept( T() ) ) {} };
92
92
! !
C++ 2003 C++ 2011
Guideline
93
93
! !
C++ 2003 C++ 2011
Standard Handlers
• The “Terminate” Handler!
94
94
! !
C++ 2003 C++ 2011
95
95-1
! !
C++ 2003 C++ 2011
95
95-2
! !
C++ 2003 C++ 2011
95
95-3
! !
C++ 2003 C++ 2011
95
95-4
! !
C++ 2003 C++ 2011
95
95-5
! !
C++ 2003 C++ 2011
95
95-6
! !
C++ 2003 C++ 2011
• Destructors!
95
95-7
Guideline
96
96-1
Guideline
96
96-2
Safe Objects
97
97
Object Lifetimes
• Order of construction:
98
98-1
Object Lifetimes
• Order of construction:
• Base class objects
98
98-2
Object Lifetimes
• Order of construction:
• Base class objects
• As listed in the type definition, left to right
98
98-3
Object Lifetimes
• Order of construction:
• Base class objects
• As listed in the type definition, left to right
• Data members
98
98-4
Object Lifetimes
• Order of construction:
• Base class objects
• As listed in the type definition, left to right
• Data members
• As listed in the type definition, top to bottom
98
98-5
Object Lifetimes
• Order of construction:
• Base class objects
• As listed in the type definition, left to right
• Data members
• As listed in the type definition, top to bottom
• Not as listed in the constructor’s initializer list
98
98-6
Object Lifetimes
• Order of construction:
• Base class objects
• As listed in the type definition, left to right
• Data members
• As listed in the type definition, top to bottom
• Not as listed in the constructor’s initializer list
• Constructor body
98
98-7
Object Lifetimes
• Order of construction:
• Base class objects
• As listed in the type definition, left to right
• Data members
• As listed in the type definition, top to bottom
• Not as listed in the constructor’s initializer list
• Constructor body
• Order of destruction:
98
98-8
Object Lifetimes
• Order of construction:
• Base class objects
• As listed in the type definition, left to right
• Data members
• As listed in the type definition, top to bottom
• Not as listed in the constructor’s initializer list
• Constructor body
• Order of destruction:
• Exact reverse order of construction
98
98-9
Object Lifetimes
• Order of construction:
• Base class objects
• As listed in the type definition, left to right
• Data members
• As listed in the type definition, top to bottom
• Not as listed in the constructor’s initializer list
• Constructor body
• Order of destruction:
• Exact reverse order of construction
• When does an object’s lifetime begin?
98
98-10
Aborted Construction
• How?
• Throw from constructor of base class,
constructor of data member, constructor body
• What do we need to clean up?
• Base class objects?
99
99-1
Aborted Construction
• How?
• Throw from constructor of base class,
constructor of data member, constructor body
• What do we need to clean up?
• Base class objects?
• Data members?
99
99-2
Aborted Construction
• How?
• Throw from constructor of base class,
constructor of data member, constructor body
• What do we need to clean up?
• Base class objects?
• Data members?
• Constructor body?
99
99-3
Aborted Construction
• How?
• Throw from constructor of base class,
constructor of data member, constructor body
• What do we need to clean up?
• Base class objects?
• Data members?
• Constructor body?
• We need to clean up anything we do here
because the destructor will not be called.
99
99-4
Aborted Construction
• How?
• Throw from constructor of base class,
constructor of data member, constructor body
• What do we need to clean up?
• Base class objects?
• Data members?
• Constructor body?
• We need to clean up anything we do here
because the destructor will not be called.
• What about new array?
99
99-5
Aborted Construction
• How?
• Throw from constructor of base class,
constructor of data member, constructor body
• What do we need to clean up?
• Base class objects?
• Data members?
• Constructor body?
• We need to clean up anything we do here
because the destructor will not be called.
• What about new array?
• What about the object’s memory?
99
99-6
Aborted Construction
100
100-1
Aborted Construction
100
100-2
Aborted Construction
100
100-3
Aborted Construction
100
100-4
Placement New
• Any use of new passing additional
parameter!
• Standard has “original placement new”!
• Overload for “newing” an object in place!
Object* obj = new(&buffer) Object;!
101
101
Aborted Construction
102
102-1
Aborted Construction
102
102-2
Placement Delete
103
103-1
Placement Delete
103
103-2
RAII
104
104
RAII Examples
• Most smart pointers!
• Many wrappers for!
• memory!
• files!
• mutexes!
• network sockets!
• graphic ports
105
105
What happens to the
object if acquisition fails?
106
106-1
• Nothing
106
106-2
What happens to the
object if acquisition fails?
107
107
RAII Cleanup
108
108
Design Guideline
109
109
Every Resource in a
Object
110
110
! !
C++ 2003 C++ 2011
shared_pointer
111
• Is this safe?
FooBar(smart_ptr<Foo>(new Foo(f)),
smart_ptr<Bar>(new Bar(b)));
112
112-1
Smart Pointer “Gotcha”
• Is this safe?
FooBar(smart_ptr<Foo>(new Foo(f)),
smart_ptr<Bar>(new Bar(b)));
112
112-2
113
113-1
Smart Pointer “Gotcha”
113
113-2
113
113-3
Smart Pointer “Gotcha”
114
114-1
114
114-2
Smart Pointer “Gotcha”
114
114-3
115
115-1
Smart Pointer “Gotcha”
115
115-2
Dimov’s rule
116
116
Smart Pointer “Gotcha”
• A better way!
!
auto r(std::make_shared<Foo>(f));!
auto s(sutter::make_unique<Foo>(f));!
!
• More efficient.!
• Safer
117
117
• Is this safe?
FooBar(std::make_shared<Foo>(f),
std::make_shared<Bar>(b));
118
118-1
Smart Pointer “Gotcha”
• Is this safe?
FooBar(std::make_shared<Foo>(f),
std::make_shared<Bar>(b));
Yes!
118
118-2
• A better rule!
!
“Don’t call new.”!
119
119
Smart Pointer “Gotcha”
• A better rule!
!
“Don’t call new.”!
“Avoid calling new.”
120
120
Lesson Learned
121
121
Manage State Like a
Resource
122
122
RAII
123
123
RAII
124
124-1
RAII
124
124-2
RAII
124
124-3
RAII
124-4
Guideline
• Use RAII.!
• Responsibility Acquisition Is Initialization.!
• Every responsibility is an object!
• One responsibility per object
125
125
Cleanup Code
126
126
Joel on Software
dosomething();!
cleanup();
127
127
Jon on Software
{!
! CleanupType cleanup;!
! dosomething();!
}!
128
128
Guideline
129
129
130
The Cargill Widget
Example
Widget& Widget::operator=(Widget const& rhs) {!
T1 original(t1_);!
t1_ = rhs.t1_;!
try {!
t2_ = rhs.t2_;!
} catch (...) {!
t1_ = original;!
throw;!
}!
}
131
131
132
The Cargill Widget
Example
• Cargill’s Points!
• Exception-safety is harder than it looks.!
• It can’t be “bolted on” after the fact.!
• It need to be designed in from the beginning.
133
133-1
133
133-2
The Cargill Widget
Example
• Cargill’s Points!
• Exception-safety is harder than it looks.!
• It can’t be “bolted on” after the fact.!
• It need to be designed in from the beginning.
• Cargill’s answer to the challenge:!
• No, it can’t be done.
• Jon’s answer:!
• Yes, it can.
133
133-3
!
C++ 2003
Fundamental Object
Functions
• Construction!
• Default!
• Copy
• Destruction
• (Copy) Assignment operator!
• Value class
• The Rule of Three
134
134-1
!
C++ 2003
Fundamental Object
Functions
• Construction!
• Default!
• Copy
• Destruction
• (Copy) Assignment operator!
• Value class
• The Rule of Three
• The Rule of Four!
• One more fundamental operator…
134
134-2
!
C++ 2003
The Swapperator
• swap()!
• No-Throw swapping is a key exception-safety tool!
• swap() is defined in std, but...!
• std::swap<>() not No-Throw (in classic C++)!
• swap() for types we define can (almost) always be
written as No-Throw
135
135
!
C++ 2003
The Swapperator
• Spelled “swap()”!
• Write a one-parameter member function and two-
parameter free function in the “std” namespace!
136
!
C++ 2003
Swapperator Examples
struct BigInt {!
! …!
!void swap(BigInt&); // No Throw!
// swap bases, then members!
! …
};!
namespace std {!
template <> void swap<BigInt>(BigInt&a, BigInt&b)!
{a.swap(b);}!
}
137
137
!
C++ 2003
Swapperator Examples
template <typename T>!
struct CircularBuffer {!
! …!
! void swap(CircularBuffer<T>&); // No Throw!
! // Implementation will swap bases then members.!
! …!
};!
// not in namespace std!
template <typename T>!
void swap(CircularBuffer<T>&a, CircularBuffer<T>&b)
{a.swap(b);}
138
138
!
C++ 2003
Why No-Throw?
• That is the whole point!
• std::swap<>() is always an option!
• But it doesn’t promise No-Throw!
• It does three copies–Copies can fail!!
• 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
139
139
!
C++ 2003
Guideline
140
140
!
C++ 2011
The Swapperator
141
141
!
C++ 2011
The Swapperator
142
142
!
C++ 2011
The Swapperator
• New rules for move operations
• Kind of based on Rule of Three
• If we create copy operations we must
create our own move operations
143
143-1
!
C++ 2011
The Swapperator
• New rules for move operations
• Kind of based on Rule of Three
• If we create copy operations we must
create our own move operations
143
143-2
!
C++ 2011
The Swapperator
• New rules for move operations
• Kind of based on Rule of Three
• If we create copy operations we must
create our own move operations
143-3
!
C++ 2011
The Swapperator
esc::check_swap() will verify at compile time that
its argument's swapperator is declared noexcept!
!
#include "esc.hpp"!
!
template <typename T>!
void check_swap(T* = 0);!
!
(Safe, but useless, in C++ 2003)
144
144
!
C++ 2011
The Swapperator
#include "esc.hpp"!
!
{!
std::string a;!
! esc::check_swap(&a);!
! esc::check_swap<std::vector<int>>();!
}
145
145
!
C++ 2011
The Swapperator
#include "esc.hpp"!
struct MyType…!
{!
! …!
! void AnyMember() {esc::check_swap(this); …}!
! …!
}
146
146
!
C++ 2011
The Swapperator
template <typename T> void check_swap(T* const t = 0)!
{!
static_assert(noexcept(delete t), "msg...");!
static_assert(noexcept(T(std::move(*t))), "msg...");!
static_assert(noexcept(*t = std::move(*t)), "msg...");!
using std::swap;!
static_assert(noexcept(swap(*t, *t)), "msg...");!
}
147
147
!
C++ 2011
The Swapperator
template <typename T> void check_swap(T* const t = 0)!
{!
...!
static_assert(!
std::is_nothrow_move_constructible<T>::value, "msg...");!
static_assert(!
std::is_nothrow_move_assignable<T>::value, "msg...");!
...!
}
148
148
template…!
{!
! …!
! using std::swap;!
! swap(a, b);!
! …!
}
149
149
Calling swap in a template
(alternative)
#include "boost/swap.hpp"!
boost::swap(a, b);
150
150
!
C++ 2003
Guideline
151
151
!
C++ 2003
Guideline
152
152
! !
C++ 2003 C++ 2011
Guideline
153
153
Guideline
154
154
! !
C++ 2003 C++ 2011
Guideline
• Do not use dynamic exception specifications.
155
155-1
! !
C++ 2003 C++ 2011
Guideline
• Do not use dynamic exception specifications.
• Do use noexcept.
155
155-2
! !
C++ 2003 C++ 2011
Guideline
• Do not use dynamic exception specifications.
• Do use noexcept.
• Cleanup
• Destructors are noexcept by default
• Move/swap
• Where else?
155
155-3
! !
C++ 2003 C++ 2011
Guideline
• Do not use dynamic exception specifications.
• Do use noexcept.
• Cleanup
• Destructors are noexcept by default
• Move/swap
• Where else?
• Wherever we can?
155
155-4
! !
C++ 2003 C++ 2011
Guideline
• Do not use dynamic exception specifications.!
• Do use noexcept.!
• Cleanup!
• Destructors are noexcept by default!
• Move/swap!
• Where else?!
• Wherever it is “natural” and free?
156
156
! !
C++ 2003 C++ 2011
Guideline
• Do not use dynamic exception specifications.!
• Do use noexcept.!
• Cleanup!
• Destructors are noexcept by default!
• Move/swap!
• Where else?!
• No where!
157
157
158
158
struct ResourceOwner!
{!
! ! // …!
! ! ResourceOwner& operator=(ResourceOwner const&rhs)!
! ! {!
! ! ! delete mResource;!
! ! ! mResource = new Resource(*rhs.mResource);!
! ! ! return *this;!
! ! }!
! ! // …!
! private:!
! ! // …!
! ! Resource* mResource;!
};
159
159
struct ResourceOwner!
{!
! ! // …!
! ! ResourceOwner& operator=(ResourceOwner const&rhs)!
! ! {!
! ! ! if (this != &rhs)!
! ! ! {!
! ! ! ! delete mResource;!
! ! ! ! mResource = new Resource(*rhs.mResource);!
! ! ! }!
! ! ! return *this;!
! ! }!
! ! // …!
! private:!
! ! // …!
! ! Resource* mResource;!
}; 160
160
struct ResourceOwner!
{!
! ! // …!
! ! ResourceOwner& operator=(ResourceOwner const&rhs)!
! ! {!
! ! ! if (this != &rhs)!
! ! ! {!
! ! ! ! Resource temp(*rhs.mResource);!
! ! ! ! temp.swap(*mResource);!
! ! ! }!
! ! ! return *this;!
! ! }!
! ! // …!
! private:!
! ! // …!
! ! Resource* mResource;!
}; 161
161
struct ResourceOwner!
{!
! ! // …!
! ! ResourceOwner& operator=(ResourceOwner const&rhs)!
! ! {!
! ! ! Resource temp(*rhs.mResource);!
! ! ! temp.swap(*mResource);!
! ! ! return *this;!
! ! }!
! ! // …!
! private:!
! ! // …!
! ! Resource* mResource;!
};
162
162
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);!
}
163
163
struct ResourceOwner!
{!
! ! // …!
! ! ResourceOwner& operator=(ResourceOwner const&rhs)!
! ! {!
! ! ! Resource temp(*rhs.mResource);!
The Critical Line!
! ! ! temp.swap(*mResource);!
! ! ! return *this;!
! ! }!
! ! // …!
! private:!
! ! // …!
! ! Resource* mResource;!
};
164
164
struct ResourceOwner!
{!
! ! // …!
! ! void swap(ResourceOwner&); // No Throw!
! ! ResourceOwner& operator=(ResourceOwner rhs)!
! ! {!
! ! ! swap(rhs);!
! ! ! return *this;!
! ! }!
! ! // …!
! private:!
! ! // …!
! ! Resource* mResource;!
};
165
165
!
C++ 2003
struct ResourceOwner!
{!
! ! // …!
! ! void swap(ResourceOwner&); // No Throw!
! ! ResourceOwner& operator=(ResourceOwner rhs)!
! ! {!
! ! ! swap(rhs);!
! ! ! return *this;!
! ! }!
! ! // …!
! private:!
! ! // …!
! ! Resource* mResource;!
};
166
166
!
C++ 2011
struct ResourceOwner!
{!
! ! // …!
! ! void swap(ResourceOwner&) noexcept;!
! ! ResourceOwner& operator=(ResourceOwner rhs);!
! ! ResourceOwner& operator=(ResourceOwner&& rhs) noexcept;!
!
!
!
! ! // …!
! private:!
! ! // …!
! ! Resource* mResource;!
};
167
167
!
C++ 2011
struct ResourceOwner!
{!
! ! // …!
! ! void swap(ResourceOwner&) noexcept;!
! ! ResourceOwner& operator=(ResourceOwner const&rhs);!
! ! ResourceOwner& operator=(ResourceOwner&& rhs) noexcept;!
!
!
!
! ! // …!
! private:!
! ! // …!
! ! Resource* mResource;!
};
168
168
!
C++ 2011
struct ResourceOwner!
{!
! ! // …!
! ! void swap(ResourceOwner&) noexcept;!
! ! ResourceOwner& operator=(ResourceOwner const&rhs)!
! ! {!
! ! ! ResourceOwner temp(rhs);!
! ! ! swap(temp);!
! ! ! return *this;!
! ! }!
! private:!
! ! // …!
! ! Resource* mResource;!
};
169
169
Guideline
170
170
The Cargill Widget
Example
171
171-1
171
171-2
The Cargill Widget
Example
!
Widget& Widget::operator=(Widget const& rhs) {!
T1 tempT1(rhs.t1_);!
T2 tempT2(rhs.t1_);!
The Critical Line!
t1_.swap(tempT1);!
t2_.swap(tempT2);!
}!
!
// Strong Guarantee achieved!!
172
172
swap()
173
173
Where to try/catch
• Switch!
• Strategy!
• Some success
174
174
Switch
175
175
Switch Cases
• Anywhere that we support the No-Throw
Guarantee!
176
Strategy
177
177
Some Success
178
178
Guideline
179
179
“Most Important
Design Guideline”
180
180
“Most Important
Design Guideline”
181
181
“Most Important
Design Guideline”
ErrorCode SomeCall(...);!
void SomeCall(...); // throws
182
182
Guideline
183
183
Prefer Exceptions to
Error Codes
• Throwing exceptions should be mostly about
resource availability !
184
Exception-Safety
Guidelines
• Throw by value. Catch by reference.!
185
Implementation
Techniques
• on_scope_exit!
• Lippincott Functions!
• boost::exception!
• Transitioning from legacy code!
• Before and After
186
186
on_scope_exit
187
187
void CTableLabelBase::TrackMove( ... )! // This function!
! // needs to set the cursor to the grab hand while it!
{!
! //! executes and set it back to the open hand afterwards.!
! ...!
!
! esc::on_scope_exit handRestore(&UCursor::SetOpenHandCursor);!
!
! UCursor::SetGrabHandCursor();!
! ...!
}
188
188
void JoelsFunction()!
{!
! dosomething();!
cleanup();!
}
189
189
void JoelsFunction()!
{!
! esc::on_scope_exit clean(cleanup);!
dosomething();!
}
190
190
struct on_scope_exit !
{!
typedef function<void(void)> exit_action_t;!
!
on_scope_exit(exit_action_t action): action_(action) {}!
~on_scope_exit() {if (action_) action_();}!
! ! void set_action(exit_action_t action = 0) {action_ = action;}!
! ! void release() {set_action();}!
!
private:!
on_scope_exit();!
on_scope_exit(on_scope_exit const&);!
on_scope_exit& operator=(on_scope_exit const&rhs);!
exit_action_t action_;!
};
191
191
on_scope_exit source
192
192
Lippincott Functions
193
193
C_APIStatus C_APIFunctionCall()!
{!
! C_APIStatus result(kC_APINoError);!
! try!
! {!
! ! CodeThatMightThrow();!
! }!
! catch (FrameworkException const& ex)!
! {result = ex.GetErrorCode();}!
! catch (Util::OSStatusException const&ex)!
! {result = ex.GetStatus();}!
! catch (std::exception const&)!
! {result = kC_APIUnknownError;}!
! catch (...)!
! {result = kC_APIUnknownError;}!
! return result;!
} 194
194
C_APIStatus C_APIFunctionCall()!
{!
! C_APIStatus result(kC_APINoError);!
! try!
! {!
! ! CodeThatMightThrow();!
! }!
! catch (…)!
! {!
! ! result = ErrorFromException();!
! }!
! return result;!
}
195
195
C_APIStatus ErrorFromException()!
{!
! C_APIStatus result(kC_APIUnknownError);!
! try!
! { throw; }!// rethrows the exception caught in the caller’s catch block.!
! catch (FrameworkException const& ex)!
! { result = ex.GetErrorCode(); }!
! catch (Util::OSStatusException const&ex)!
! { result = ex.GetStatus(); }!
! catch (std::exception const&) { /* already kC_APIUnknownError */ }!
! catch (...) { /* already kC_APIUnknownError */ }!
! if (result == noErr) { result = kC_APIUnknownError; }!
! return result;!
}
196
196
boost::exception
• An interesting implementation to support
enhanced trouble-shooting.!
197
Legacy Code
198
198
Sean’s Rules
1.All new code is written to be exception safe!
2.Any new interfaces are free to throw an
exception!
199
199
Refactoring Steps
a. Consider implementing a parallel call and
re-implementing the old in terms of the new
200
200
Refactoring Steps
1.Implement a parallel call following exception
safety guidelines!
201
Refactoring Steps
• Moving an large legacy code base still a big
chore!
202
202
Example Code
• CreateReadOnlyForCurrentUserACL()!
• “mbr_” and “acl_” APIs return non-zero
error codes on error
203
203
static acl_t CreateReadOnlyForCurrentUserACL(void)!
{!
acl_t theACL = NULL;!
uuid_t theUUID;!
! int result;!
! acl_permset_t newPermSet;!
204
Example Code
• Rewrite Assumptions!
• All “mbr_” and “acl_” APIs throw!
• acl_t RAII Wrapper Class
205
205
Example Rewrite
206
206
207
static acl_t CreateReadOnlyForCurrentUserACL()!
{!
ACL theACL(1);!
acl_entry_t newEntry;!
acl_create_entry_np(&theACL.get(), &newEntry, ACL_FIRST_ENTRY);!
!
// allow!
acl_set_tag_type(newEntry, ACL_EXTENDED_ALLOW);!
!
// the current user!
uuid_t theUUID;!
mbr_uid_to_uuid(geteuid(), theUUID); // need the uuid for the ACE!
acl_set_qualifier(newEntry, (const void *)theUUID);!
acl_permset_t newPermSet;!
acl_get_permset(newEntry, &newPermSet);!
!
// to read data!
acl_add_perm(newPermSet, ACL_READ_DATA);!
acl_set_permset(newEntry, newPermSet);!
!
// all set up and ready to go!
return theACL.release();!
}!
208
208
• Advantages!
• More white space!
• 50% fewer lines!
• 100% fewer braces!
• 100% fewer control structures!
• Easier to write and read, faster, and 100%
robust
209
209
What does Exception-
Safe Code look like?
210
210
211
211
The Success Path
212
212
213
The Promise
• Easier to Read!
Easier to Understand and Maintain!
• Easier to Write !
• No time penalty!
• 100% Robust
214
214
The Promise
• Why easier to read and write?!
• Many fewer lines of code!
• No error propagation code!
• Focus on the success path only
215
215
The Promise
• Why no time penalty?!
• As fast as if errors handling is ignored!!
• No return code checking!
• Compiler knows error handling code!
• catch blocks can be appropriately
(de)optimitized
216
216
The Promise
• Why 100% robust?!
• Errors are never ignored!
• Errors do not leave us in bad states!
• No leaks
217
217
Thank you
• Visit:!
http://exceptionsafecode.com!
218
Exception-Safe Coding
Questions?
219