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;
}