15. Exception Handling
15.1 Throwing an Exception
15.1.1 Only use instances of std::exception for exceptions
Exceptions pass information up the call stack to a point where error handling can be performed. If an object of class type is thrown, the class type itself serves to document the cause of an exception. Only types that inherit from std::exception, should be thrown.
#include <cstdint> #include <stdexcept> #include <iostream> int foo (); void bar () { try { if (0 == foo ()) { throw -1; // @@- Non-Compliant -@@ } } catch (int32_t e) // @@- Non-Compliant -@@ { } try { if (0 == foo ()) { throw std::runtime_error ("unexpected condition"); // @@+ Compliant +@@ } } catch (std::exception const & e) // @@+ Compliant +@@ { std::cerr << e.what (); } }
If an instance of an object inheriting from std::exception is created, then such an object must appear in a throw expression.
#include <exception> class MyException : public std::exception { // ... }; void f1 () { MyException myExcp; // @@- Non-Compliant -@@ } void f2 () { MyException myExcp; // @@+ Compliant +@@ throw myExcp; }
References
- HIC++ v3.3 – 9.2
15.2 Constructors and Destructors
15.2.1 Do not throw an exception from a destructor
The 2011 C++ language standard states that unless a user provided destructor has an explicit exception specification, one will be added implicitly, matching the one that an implicit destructor for the type would have received.
#include <stdexcept> class A { public: ~A () // @@- Non-Compliant: Implicit destructor for A would be declared with -@@ // @@- noexcept, therefore this destructor is noexcept -@@ { throw std::runtime_error ("results in call to std::terminate"); } };
Furthermore when an exception is thrown, stack unwinding will call the destructors of all objects with automatic storage duration still in scope up to the location where the exception is eventually caught. The program will immediately terminate should another exception be thrown from a destructor of one of these objects.
#include <cstdint> #include <stdexcept> class A { public: A () : m_p () {} ~A () noexcept(false) { if (nullptr == m_p) { throw std::runtime_error ("null pointer in A"); // @@- Non-Compliant -@@ } } private: int32_t * m_p; }; void foo (int32_t i) { if (i < 0) { throw std::range_error ("i is negative"); } } void bar () { try { A a; foo (-1); } catch (std::exception const & e) { } }
References
- HIC++ v3.3 – 9.1
15.3 Handling an Exception
15.3.1 Do not access non-static members from a catch handler of constructor/destructor function try block
When a constructor or a destructor has a function try block, accessing a non-static member from an associated exception handler will result in undefined behavior.
#include <cstdint> class C { public: C (); private: int32_t m_i; }; C::C () try : m_i() { // constructor body ++m_i; // @@+ Compliant +@@ } catch (...) { --m_i; // @@- Non-Compliant -@@ }
References
- MISRA C++:2008 – 15-3-3
15.3.2 Ensure that a program does not result in a call to std::terminate
The path of an exception should be logical and well defined. Throwing an exception that is never subsequently caught, or attempting to rethrow when an exception is not being handled is an indicator of a problem with the design.
bool f1 (); void f2 () { throw; } void f3 () { try { if (f1 ()) { throw float(0.0); } else { f2(); // @@- Non-Compliant: No current exception -@@ } } catch (...) { f2(); } } int main () { f3(); // @@- Non-Compliant: If 'float' thrown -@@ }
References
- MISRA C++:2008 – 15-3-2