1

I would like to create a class and enum to handle errors in my project. As of now I am doing it in the below way.

enum class eErrorType
{
    eJsonFileNotFound = 0,
    eJsonInvalidFormat,
    eJsonKeyNotFound,
    eJsonEmptyArray,
    eNoError,
    eCustom
};


class Error
{
public:
    // Constructors                                                                     
    Error() { errorType = eErrorType::eNoError; message = ""; }
    Error(eErrorType type) { errorType = type; SetMessage(type); }
    Error(std::string msg) { errorType = eErrorType::eCustom; message = msg; }

    // Public Methods                                                                   
    std::string getErrMessage() { return message; }


private:

    eErrorType errorType;
    std::string message;
    void SetMessage(eErrorType type)
    {
        switch (type)
        {
        case eErrorType::eJsonFileNotFound: message = "Json file not found"; break;
        case eErrorType::eJsonInvalidFormat: message = "Invalid json file"; break;
        case eErrorType::eJsonKeyNotFound: message = "Specified key is not found in json"; break;
        case eErrorType::eJsonEmptyArray: message = "No elements in json array"; break;
        case eErrorType::eNoError: message = "Entry contained an attempt to divide by zero!"; break;
        default: message = ""; break;
        }
    }
};

int main()
{
    try
    {
        //open json file. If file open failed, throw error
        throw eErrorType::eJsonFileNotFound;

        //parse json file. If parsing failed, throw error
        throw eErrorType::eJsonInvalidFormat;

        //check for particular key in the json. If not found, throw error
        throw eErrorType::eJsonKeyNotFound;
    }
    catch (eErrorType errCode)
    {
        Error errObj(errCode);
        std::cout << errObj.getErrMessage() << std::endl;
    }

    return 0;
}

I would like some suggestions for improvements. Is there any better way of doing it or any language based features are available to achieve this.

Arun
  • 2,247
  • 3
  • 28
  • 51
  • 1
    I'm voting to close this question as off-topic because this question belongs to stackreview exchange – KamilCuk Apr 09 '19 at 07:52
  • Yes. It's strange to store both `eErrorType` and `message` in the error, if the transformation from `eErrorType` to `string` is static and constexpr. You could just `static const char *eErrorType_to_string(type)` and provide `operator std::string ()`. Also, it's better to use `const char *` in error handling, `std::string` can fail with out of memory. – KamilCuk Apr 09 '19 at 07:53
  • Have you looked at `std::error_code`? – Eric Apr 12 '19 at 08:00

2 Answers2

3

For custom errors you can inherit from std::exception, override exception methods and implement your own stuff, example:

#include <exception>    // std::exception

//
// custom exception class
//
    class error final :
        public std::exception
    {
    public:
        error(const char* description, short code = -1) throw() :
            description(description), code(code) { }

        const char* what() const throw() override
        {
            return description;
        }

        short Code() const throw()
        {
            return code;
        }

        error(error&& ref)
            : description(ref.description), code(ref.code) { }

        error& operator=(error&&)
        {
            return *this;
        }

        error(const error& ref)
            : description(ref.description), code(ref.code) { }

    private:
        const char* description;
        const short code;
        error& operator=(const error&) = delete;
    };

Define a macro to show the filename where the error occured:

#include <cstring>      // std::strrchr
// Show only file name instead of full path
#define __FILENAME__ (std::strrchr(__FILE__, '\\') ? std::strrchr(__FILE__, '\\') + 1 : __FILE__)

Define universal function to show the error (followin implemetation shows message box but you can redefine it for console program)

#include <string>
#include <windows.h>
#include <codecvt>      // string conversion std::wstring_convert and std::codecvt_utf8

//
// converts string or const char* to wstring
//
std::wstring stringToWstring(const std::string& t_str)
{
    //setup converter
    typedef std::codecvt_utf8<wchar_t> convert_type;
    std::wstring_convert<convert_type, wchar_t> converter;

    //use converter (.to_bytes: wstr->str, .from_bytes: str->wstr)
    return converter.from_bytes(t_str);
}

//
// Set error message to your liking using error class
// and show message box, this function is also used to pass in
// std::exception objects
//
template <typename ExceptionClass>
void ShowError(
    HWND hWnd,
    ExceptionClass exception,
    const char* file,
    int line,
    long info = MB_ICONERROR)
{
    std::string error_type = TEXT("Rutime Error");
    std::string error_message = TEXT("File:\t");

#ifdef UNICODE
    error_message.append(stringToWstring(file));
#else
    error_message.append(file);
#endif // UNICODE

    error_message.append(TEXT("\r\nLine:\t"));
    error_message.append(std::to_string(line));
    error_message.append(TEXT("\r\nError:\t"));

#ifdef UNICODE
    error_message.append(stringToWstring(exception.what()));
#else
    error_message.append(exception.what());
#endif // UNICODE

    // Show error message
    MessageBox(hWnd,
        error_message.c_str(),
        error_type.c_str(), static_cast<UINT>(MB_OK | info));
}

You then show the error like this:

ShowError(nullptr, error("You error message"), __FILENAME__, __LINE__);

For Win32/COM error types the function can be overloaded like this:

#include <comdef.h>     // _com_error
#include <windows.h>
#include <string>
//
// Format error code into a string and show message box
// COM and System errors
//
void ShowError(HWND hWnd, const char* file, int line, HRESULT hr = S_OK)
{
    string error_type = TEXT("Rutime Error");
    string error_message = TEXT("File:\t");

#ifdef UNICODE
    error_message.append(stringToWstring(file));
#else
    error_message.append(file);
#endif // UNICODE

    error_message.append(TEXT("\r\nLine:\t"));
    error_message.append(std::to_string(line));
    error_message.append(TEXT("\r\nError:\t"));

    // If HRESULT is omited or S_OK
    // format last error code message
    if (hr == S_OK)
    {
        LPVOID lpBuff = nullptr;

        DWORD dwChars = FormatMessage(
            FORMAT_MESSAGE_ALLOCATE_BUFFER |
            FORMAT_MESSAGE_FROM_SYSTEM,
            nullptr,
            GetLastError(),
            0,
            reinterpret_cast<LPTSTR>(&lpBuff),
            0,
            nullptr);

        // If the function succeeds, the return value is
        // the number of TCHARs stored in the output buffer
        if (dwChars)
        {
            error_message.append(reinterpret_cast<LPCTSTR>(lpBuff));
        }
        else // If the function fails, the return value is zero
        {
            error_message.append(TEXT("Unknown Error\t"));
            error_message.append(to_string(GetLastError()));
        }

        // Free the buffer allocated by FormatMessage
        LocalFree(lpBuff);
    }
    else // Format com error code into a message
    {
        _com_error err(hr);
        error_message.append(err.ErrorMessage());
    }

    // Play the sound and show error message
    MessageBox(hWnd,
        error_message.c_str(),
        error_type.c_str(), MB_OK | MB_ICONERROR);
}

The function is called slight differently for system errors:

ShowError(nullptr, __FILENAME__, __LINE__); // type hresult if needed

edit: I copied the code from my project, and currently std::to_string where mentioned works only for ANSI version, you need to modify ShowError function to conditionaly use std::to_wstring for unicode. Also string inside ShowError function is ANSI string, you can conditionally use wstring or define a macro for string if you wan't like this:

#ifdef UNICODE
    typedef std::wstring string;
#else
    typedef std::string string;
#endif // UNICODE

also for to_string if you wish:

// conditionaly use string or wide string
#ifdef UNICODE
#define to_string std::to_wstring
#else
#define to_string std::to_string
#endif // UNICODE

You can also implement enum class code types and pass them to exception class as second or 3rd additional argument and implement showing the custom error code if you wish to avoid typing error message for every separate function call.

Also note that ShowError function can be used for std errors inside catch statement, where you simply pass std error object like this for example:

try
{
       // example, or do some heavy memory allocation here to throw
       throw std::bad_alloc;
}
catch(std::bad_alloc& err);
{
       ShowError(nullptr, err, __FILENAME__, __LINE__);
}

This approach can be extended to modify the function to also format NTSTATUS messages

For complete list of possible error messages in Win32 see this.

For additional information on functions used in above code see following link:

FormatMessage function

GetLastError function

Some of the code has been copied from this site ex:

Convert to wstring

Show only file name

Format COM Error code

  • I want to define all the error messages in one place. Because many places the error message is common like file open failed, parsing failed. If I implement in a way you suggested then every place I have to update the error message. – Arun Apr 09 '19 at 09:24
  • Yes, basically you just need to add enum class variable into the exception class and define a constructor that will initialize the member with your enum message, and then pass Exception class to ShowError with enum of the message. additional function to convert enum to string is needed in that case in either exception class or ShowError function. –  Apr 09 '19 at 09:31
  • This answer starts off well, but turns into an undirected journey though a bunch of stuff that you've presumably used around errors in the past – Eric Apr 12 '19 at 08:03
1

Lets start with the headers.

#include <system_error>   // For std::error_code
#include <exception>      // For std::exception
#include <iostream>       // To print example output

Often you are stuck with some macros (could be constexpr int's)

#define FOO_ERROR1 1
#define FOO_ERROR2 2

You should be working in a namespace, lets use n here.

namespace n {

Step 1. define an enum type (you might already HAVE an enum, which is good).

enum class Error {
  XE_FOO_ERROR1 = FOO_ERROR1, // XE_ is a random prefix to distinguish it from the macro FOO_ERROR1.
  XE_FOO_ERROR2 = FOO_ERROR2
};

Step 2. define the following functions that will be found through ADL (Argument-dependent lookup). This is why the namespace (n) should be very specific for this error type. As an example, the namespace that I use for errors of my xcb system is xcb::errors::org::freedesktop::xcb. Just n is probably too short, and we should be using foo anyway, but I think it is less confusing for the example to use different names.

std::string to_string(Error code);                        // You probably want those enums to be convertible to strings with `to_string`.
std::ostream& operator<<(std::ostream& os, Error code);   // In order to print the enum's to an ostream.
inline char const* get_domain(Error) { return "foo:org.freedesktop.foo.Error"; }  // An error domain string.
std::error_code make_error_code(Error code) noexcept;     // Converting the Error to a std::error_code.

} // namespace n

Step 3. register n::Error as valid error code.

namespace std {
template<> struct is_error_code_enum<n::Error> : true_type { };
} // namespace std

And yes, it is legal to define this in namespace std. It is what is intended by the standard.

Next, lets define the above functions in our .cpp file:

namespace n {

// Implement to_string
std::string to_string(Error code)
{
#if 0
  switch(code)
  {
    case Error::XE_FOO_ERROR1:
      return "FOO_ERROR1";
    case Error::XE_FOO_ERROR2:
      return "FOO_ERROR2";
  }
  return "Never reached";
#else
  // Or if you use magic_enum (https://github.com/Neargye/magic_enum), we can use the simple:
  auto sv = magic_enum::enum_name(code);
  sv.remove_prefix(3);  // Remove "XE_" from the name.
  return std::string{sv};
#endif
}

// Implement operator<<
std::ostream& operator<<(std::ostream& os, Error code)
{
  return os << to_string(code);
}

Before we can define make_error_code we first need to define the error category (this is still in that same .cpp file!):

namespace {

struct FooErrorCategory : std::error_category
{
  char const* name() const noexcept override;
  std::string message(int ev) const override;
};

char const* FooErrorCategory::name() const noexcept
{
  return "n::Error"; // Can be anything you want.
}

std::string FooErrorCategory::message(int ev) const
{
  auto error = static_cast<Error>(ev);
  return to_string(error);
}

FooErrorCategory const theFooErrorCategory { };

} // namespace

And now we can define make_error_code.

std::error_code make_error_code(Error code) noexcept
{
  return std::error_code(static_cast<int>(code), theFooErrorCategory);
}

} // namespace n

In order to support exceptions we need some exception class that supports std::error_code. This is a general class thus, not something specific to one n::Error enum. Personally I use a specialized system that you can find here, but lets just code something minimal for this example (this is the header file):

namespace errors {

struct ErrorCode : public std::exception
{
  std::error_code m_ec;
  char const* m_text;

  ErrorCode(std::error_code ec, char const* text) : m_ec(ec), m_text(text) {}

  void print_on(std::ostream& os) const;
};

inline std::ostream& operator<<(std::ostream& os, ErrorCode const& ec)
{
  ec.print_on(os);
  return os; 
}

} // namespace errors

And the .cpp file

namespace errors {

void ErrorCode::print_on(std::ostream& os) const
{
  os << m_text << ": " << m_ec.message() << " [" << m_ec << "]";
}

} // namespace errors

Finally, here is some code to test the above. You can also find this complete example on wandbox.

int main()
{
  // A test enum error code.
  n::Error error = n::Error::XE_FOO_ERROR2;

  // We want to be able to use to_string.
  using std::to_string; // Fall back (that we are not using here).
  std::cout << to_string(error) << std::endl;

  // We want to be able to print an Error.
  std::cout << error << std::endl;

  // We want to be able to convert the error to a std::error_code.
  std::error_code ec = error;

  // We want to be able to print that error_code.
  std::cout << ec << " [" << ec.message() << "]\n";

  // You can convert a macro (or int) value to the enum type.
  n::Error e1 = static_cast<n::Error>(FOO_ERROR1);

  // We want to be able to throw 'Error' values.
  try
  {
    throw errors::ErrorCode(e1, "Some text");
  }
  catch (errors::ErrorCode const& error)
  {
    std::cout << "Caught: " << error << std::endl;
  }
}

Which outputs

FOO_ERROR2
FOO_ERROR2
n::Error:2 [FOO_ERROR2]
Caught: Some text: FOO_ERROR1 [n::Error:1]
Carlo Wood
  • 5,648
  • 2
  • 35
  • 47