Writing “Exception Safe” C++ Alan Griffiths –Work: Experian Limited Senior Systems Consultant Mentoring & development OOA/OOD/C++ –Play: WWW, Java –ACCU chairman
Overview This talk breaks down into the following sections: –What we mean by “exception safety” –The problems of a naïve approach –Guidelines and a rewrite –Applying these techniques
Timeline ARM: Exceptions (experimental) 1995Q1 CD1: Exceptions in language but Q4 CD2: …library not exception safe 1997ish - Many exception-safety articles ISO Standard - “Exceptions safe”
Some of the articles H Muller - “Ten rules for handling exception handling successfully” – C++ Report Jan.’96 H Sutter - “Designing exception-safe Generic Containers” – C++ Report Sept.’97 H Sutter - “More exception-safe Generic Containers” – C++ Report Nov-Dec.’97 K Henney – “Creating Stable Assignments” – C++ Report June’98
Writing “Exception Safe” C++ What we mean by “exception safety” –The problems of a naïve approach –Guidelines and a rewrite –Applying these techniques
Call stack: a() to x() a() calls b() … e() calls f() f() calls g() … x() throws an exception [handled by a()]. x() line 1... g() line bytes f() line bytes e() line bytes... a() line bytes main() line 360 mainCRTStartup() line bytes
f() is exception safe if... The weak exception safety guarantee –No resources are leaked –The system state remains valid The strong exception safety guarantee –If f() terminates by propagating an exception then it makes no change to the state of the program.
Divide and conquer f() relies on g(), h(), …, x() Objects isolate changes
Other definitions There are other definitions of exception safe. –For example “deleteable” –You may have encountered others… –Some references in the notes
Recap: “exception safety” The “weak exception safety guarantee” requires: –Resources are not leaked –The system state remains valid In addition the “strong exception safety guarantee” requires: –There is no change to the system state
Writing “Exception Safe” C++ What we mean by “exception safety” The problems of a naïve approach –Guidelines and a rewrite –Applying these techniques
How do we write f()?
class PartOne { /* omitted */ }; class PartTwo { /* omitted */ }; class Whole { public: //...Lots omitted... Whole& operator=(const Whole& rhs); private: PartOne*p1; PartTwo*p2; };
Assignment operator Whole& Whole::operator=(const Whole& rhs){ if (&rhs != this) { delete p1; delete p2; p1 = new PartOne(*rhs.p1); p2 = new PartTwo(*rhs.p2); } return *this; } Don’t do it this way!!!
Whole& Whole::operator=(const Whole& rhs) { if (&rhs != this) { PartOne* t1 = new PartOne(*rhs.p1); try { PartTwo* t2 = new PartTwo(*rhs.p2); delete p1; delete p2; p1 = t1; p2 = t2; } catch (...) { delete t1; throw; } } return *this; } The naïve approach PartOne* t1 = new PartOne(*rhs.p1); try { PartTwo* t2 = new PartTwo(*rhs.p2); delete p1; delete p2; p1 = t1; p2 = t2; } catch (...) { delete t1; throw; }
naïve approach - assessment Good software engineering: –So simple that there are “obviously no errors” This approach: –So complex that there are “no obvious errors”
Writing “Exception Safe” C++ What we mean by “exception safety” The problems of a naïve approach Guidelines and a rewrite –Applying these techniques
The guidelines There are three rules: –Destructors may not propagate exceptions –States may be swapped without an exception being thrown –An object may own at most one resource
The revised example class Whole { public: //...Lots omitted... Whole& operator=(const Whole& rhs); private: std::auto_ptr p1; std::auto_ptr p2; };
revised assignment operator Whole& Whole::operator=(const Whole& rhs) { std::auto_ptr t1(new PartOne(*rhs.p1)); std::auto_ptr t2(new PartTwo(*rhs.p2)); swap(p1, t1); swap(p2, t2); return *this; }
assignment operator - again Whole& Whole::operator=(const Whole& rhs) { Whole(rhs).swap(*this); return *this; } void Whole::swap(Whole& that) { using std::swap; swap(p1, that.p1); swap(p2, that.p2); }
Recap: the guidelines Destructors may not propagate exceptions States may be swapped without an exception being thrown An object may own at most one resource
Writing “Exception Safe” C++ What we mean by “exception safety” The problems of a naïve approach Guidelines and a rewrite Applying these techniques
A smart pointer arg::body_part_ptr<> –When an std::auto_ptr<> is copied ownership is transferred –body_part_ptr<> copies the object pointed to by the assigned pointer
Another version of Whole class Whole { public: //...Lots omitted... Whole& operator=(const Whole& rhs); void swap(Whole& that); private: arg::body_part_ptr p1; arg::body_part_ptr p2; };
The latest implementation Whole& Whole::operator=(const Whole& rhs) { Whole(rhs).swap(*this); return *this; } void Whole::swap(Whole& that) { using std::swap; swap(p1, that.p1); swap(p2, that.p2); }
Specialising std::swap namespace std { inline void swap( ::example5::Whole& lhs, ::example5::Whole& rhs) { lhs.swap(rhs); } }
Base classes Health warning don’t do this at home
Extended Whole Whole& Whole::setP1(const PartOne& value) { p1.reset(new PartOne(value)); return *this; } Whole& Whole::setP2(const PartTwo& value) { p2.reset(new PartTwo(value)); return *this; }
class ExtendedWhole : private Whole { public: /* Omit constructors & assignment */ void swap(const ExtendedWhole& rhs); void setParts( const PartOne& p1, const PartTwo& p2, const PartThree& p3); private: int count; PartThree body; };
ExtendedWhole::swap() void ExtendedWhole::swap( ExtendedWhole& rhs) { using std::swap; Whole::swap(*this); swap(count, rhs.count); swap(body, rhs.body); }
ExtendedWhole::setParts() void ExtendedWhole::setParts( const PartOne& p1, const PartTwo& p2, const PartThree& p3) { setP1(p1); setP2(p2); body = p3; }
ExtendedWhole::setParts() void ExtendedWhole::setParts( const PartOne& p1, const PartTwo& p2, const PartThree& p3) { Whole temp(*this); temp.setP1(p1).setP2(p2); body = p3; Whole::swap(temp); }
“Strong” or “Weak” guarantee? Choose a design when designing a function –Strong is more convenient for user –Weak requires fewer copies and temporaries Document the library, class or function Implement the design decision correctly I don’t know of automated tools
Writing “Exception Safe” C++ What we mean by “exception safety” The problems of a naïve approach Guidelines and a rewrite Applying these techniques
#include int main() { struct local_file { local_file() : f(std::fopen(__FILE__, "r")) {} ~local_file() { if (f) std::fclose(f); } operator std::FILE* () const { return f; } std::FILE* f; } file; char buffer[1000]; if (file) { std::fread(buffer, sizeof buffer, 1, file); // Processing that may throw an exception } return 0; } struct local_file { local_file() : f(std::fopen(__FILE__, "r")) {} ~local_file() { if (f) std::fclose(f); } operator std::FILE* () const { return f; } std::FILE* f; } file;
Example for discussion counted_ptr ptr(new T()); needs a counter allocation could fail so may constructor throw? and should it delete initialiser?
H Muller - “Ten rules for handling exception handling successfully” – C++ Report Jan.’96 H Sutter - “Designing exception-safe Generic Containers” – C++ Report Sept.’97 H Sutter - “More exception-safe Generic Containers” – C++ Report Nov-Dec.’97 K Henney – “Creating Stable Assignments” – C++ Report June’98 D Abrahams - "Exception Safety in STLport” -