#include #include #include #include #include #include // Helper macros #define ERROR_LOCATE(l) ERROR_STRINGIFY(l) #define ERROR_STRINGIFY(n) #n // Create an error object with error code and message #define ERROR_MAKE(c, m) \ Error((c), (m), __FILE__ ":" ERROR_LOCATE(__LINE__)) // Create an error object with error code and message, // wrapping an existing error object #define ERROR_WRAP(s, c, m) \ Error((s), (c), (m), __FILE__ ":" ERROR_LOCATE(__LINE__)) // // Error codes // enum ErrorCode { NO_ERROR = 0, E_BAD, E_WRONG }; // // Error class // // Encapsulates a stack of zero or more error descriptions. // Has built-in protection against ignoring returned errors. // class Error { public: // Factory method: returns a "no-error" value static Error OK() { return Error(); } // Conversion to bool: // returns true iff this object represents an error. operator bool() { return (m_desc.get() && m_desc->m_code != NO_ERROR); } // Constructor with error code, message and location Error(ErrorCode code, const std::string& message, const std::string& location) : m_desc(new Description(code, message, location)), m_handled(false) {} // Constructor with wrapped error, error code, message and location Error(const Error& child, ErrorCode code, const std::string& message, const std::string& location) : m_desc(new Description(child.release(), code, message, location)), m_handled(false) {} Error(const Error& that) : m_desc(that.m_desc) { that.m_handled = true; } Error& operator=(const Error& that) { m_desc = that.m_desc; that.m_handled = true; return *this; } // Destructor. // Prints an error message to stderr and aborts if the error was // not properly handled in its scope, unless this happens during // exception propagation. ~Error() { if (!m_handled && !std::uncaught_exception()) { std::cerr << "Unhandled error:" << std::endl; print_error_stack(std::cerr); std::abort(); } } // Signal that the error has been handled. // This method must always be called explicitly to indicate that // an error has been handled and not just ignored. void clear() { m_handled = true; } // // Print all errors in the stack // void print_error_stack(std::ostream& stream) { const Description* pdesc = m_desc.get(); if (!pdesc) stream << "no error" << std::endl; else { while (pdesc) { stream << "at " << pdesc->m_loc << ": " << 'E' << std::setfill('0') << std::setw(5) << int(pdesc->m_code) << ": " << pdesc->m_message << std::endl; pdesc = pdesc->m_child.get(); } } } // // Example accessors // // Get the topmost error code ErrorCode get_last_code() const { if (m_desc.get()) return m_desc->m_code; return NO_ERROR; } // Get the topmost error message std::string get_last_message() const { if (m_desc.get()) return m_desc->m_message; return ""; } // // The error description // class Description { friend class Error; typedef std::auto_ptr Ptr; Description(ErrorCode code, const std::string& message, const std::string& location) : m_code(code), m_message(message), m_loc(location) {} Description(Ptr child, ErrorCode code, const std::string& message, const std::string& location) : m_child(child), m_code(code), m_message(message), m_loc(location) {} Ptr m_child; // Next error in the stack, may be empty ErrorCode m_code; // Error code std::string m_message; // Specific error message std::string m_loc; // Error location for stack trace }; private: // Private default constructor indicating no error. // Accessible through Error::OK(). explicit Error() : m_handled(false) {} mutable Description::Ptr m_desc; mutable bool m_handled; Description::Ptr release() const { Description::Ptr desc(m_desc); m_handled = true; return desc; } }; namespace { void check_exception_propagation() { try { Error e = Error::OK(); // Notice that we don't clear the error, but the destructor // won't abort because it will be invoked during exception // unwind. throw std::runtime_error("not really"); } catch (...) {} } Error e0() { return Error::OK(); } Error e1() { return ERROR_MAKE(NO_ERROR, ""); } Error echild() { return ERROR_MAKE(E_WRONG, "wrong"); } Error e2() { return ERROR_WRAP(echild(), E_BAD, "bad"); } void check(Error err) { if (!err) err.clear(); else { std::cerr << "error: " << err.get_last_message() << std::endl; // Note the missing err.clear() in this branch } } } // anonymous namespace int main() { std::cout << "check_exception_propagation()" << std::endl; check_exception_propagation(); std::cout << std::endl << "check(e0())" << std::endl; check(e0()); std::cout << std::endl << "check(e1())" << std::endl; check(e1()); std::cout << std::endl << "check(e2())" << std::endl; check(e2()); // abort() due to unhandled error expected here std::cerr << "Ugh, should never have got to here" << std::endl; return 0; }