On 02.06.2015 07:48, Branko Čibej wrote: > If you don't want to use exceptions, you'll still have to write > exception-safe code: depending on the environment, exceptions can be > thrown from standard library methods. > > In general, though, I agree that using exceptions for error handling is, > whilst on the surface an obvious decision, in practice quite complex. > > I very strongly suggest not using any kind of global error state. Such > designs (see: POSIX/C errno; Windows' GetLastError; etc.) are extremely > flaky in a multi-threaded environment and hard to get right. Instead, > make all calls that can fail return a uniform error code; in C++, this > can be an enum, but it's even better to use a struct so that you can > chain/combine multiple error codes if necessarty. See, for example. > svn_error_t at line 178 in > > > http://svn.apache.org/viewvc/subversion/trunk/subversion/include/svn_types.h?view=markup > > and the various manipulation functions declared in svn_error.h in the > same directory. In C++ this can be even easier to do than in C, from the > memory management point of view.
Here's a quick prototype of what I meant by "C++ even easier than in C": An error class that has built-in protection against ignoring returned errors. > On 01.06.2015 16:01, Denis Magda wrote: >> Vladimir, >> >> One more way is to return an error code from a function. >> Example, >> int socket_write(socket *ptr, byte *buf, int buf_len); >> The function will return actual number of bytes written or -1 in case >> of error 1, -2 in case of error 2, etc.. >> Such approach is widely used by Java ME at the native level, Brew MP >> and many other platforms. >> >> Next, Apple Core Foundation returns error through a pointer passed to >> a function when it's impossible to return an error code as a return >> parameter. >> Example, >> int error; >> do_something(task, &error); >> if (error == -1) >> >> -- >> Denis >> >> On 6/1/2015 11:42 AM, Vladimir Ozerov wrote: >>> Igniters, >>> >>> In Java our API extensively use exceptions to signal faulty >>> conditions. The >>> same technique must be applied somehow to C++ API as well. >>> >>> The most obvious solution for our Java minds is to simply throw >>> exceptions >>> in C++ as well. But this solution doesn't seem to be valid for C++ >>> world: >>> 1) User will inject varoius resources into our lib (e.g. allocators, >>> serializers, closures). Throwing excpetino will make memory management >>> extremely tough from user perspective. >>> 2) Throwing exceptions across module boundaries is considered to be a >>> bad >>> practice from both portability and usability standpoints (e.g. we do not >>> want user app to crash due to, say, CachePartialUpdateException). >>> 3) Exceptions might be disabled explicitly for some apps. >>> >>> I propose to use the same approach as WinAPI, JNI and lots other >>> libraries >>> do: never throw exceptions, but rather have a separate function to check >>> for previous error. This function will return the last error occurred in >>> the thread (if any). >>> >>> References: >>> http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/functions.html#wp5234 >>> >>> (functions ExceptionOccurred, ExceptionCheck) >>> https://msdn.microsoft.com/en-us/library/windows/desktop/ms679360%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396 >>> >>> >>> Please share your thoughts regarding the matter. >>> >>> Vladimir. >>>
#include <cstdlib> #include <iomanip> #include <iostream> #include <memory> #include <stdexcept> #include <string> // 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<Description> 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; }