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

Reply via email to