开发者

Whats the right approach to return error codes in C++

开发者 https://www.devze.com 2023-01-05 04:29 出处:网络
I\'m using error codes for handling errors in my c++ project. The problem is how to return error codes from a function which is supposed to return some variable/object.

I'm using error codes for handling errors in my c++ project. The problem is how to return error codes from a function which is supposed to return some variable/object.

consider this:

long val = myobject.doSomething();

Here, myobject is an object of some class. If doSomething function encounters some开发者_StackOverflow社区 error condition then how should it notify the caller (Without using exceptions).

Possible solutions:

  1. Have a data member (say err_) in the class which can be checked by the caller. But it would be unsafe in a multi-threaded application sharing the same object and calling the same function.
  2. Use some global error variable, again same issue in a multi-threaded environment.

Now how can I notify the caller about some error condition?


Make a template called, say, Maybe that it parametrized by your return value type. Whenever you return a value, wrap it in this template like this:

Maybe<long> result = object.somemethod();

The Maybe template would have a way of being instantiated with an error code (probably a static method):

return Maybe<long>::error(code);

But ordinarily would just be returned with the value:

Maybe<long> retval;
retval = 15;
return retval;

(You would have to, of course, override the appropriate constructors, assignment operators, etc.)

In the client side you call a method to check for the error.

Maybe<long> result = object.somemethod();
if (result.is_error) 
{ 
    ... handle the error ...
}
else
{
    ... use the result ...
}

Again you'd need the appropriate operators defined to use Maybe<long> wherever there's a long required.

This sounds like a lot of work, but really the work is done once in making a good, bulletproof Maybe template. You'll also have to do some performance tuning on it to avoid nasty overheads. If you want to make it more flexible you can parametrize it on both the return value type and the error type. (This is only a minor increase in complexity.)


You probably want something like Alexandresu's Expected<T> idiom.


You can pass variable as reference and return error code in it.


You can return a std::pair holding both an error code (or error object) and the desired return object. The object of interest needs a default constructor or value so you can return something even when an error is encountered.


It is common to return a return/error code, and make available a property or member with the results.

int retCode = myobject.doSomething();
if (retCode < 0){ //Or whatever you error convention is
   //Do error handling
}else{
   long val = myobject.result;
}

It is also common to pass in a pointer that is set to the return value, and return the return/error code. (See HrQueryAllRows).

long val = INIT_VAL;
int retCode = myObject.doSomething(&val);

if (retCode < 0){
    //Do error handling
}else{
    //Do something with val...
}


You have three options:

  • Create a class containing the return value and a possible error code.

  • Use something like boost::optional for the return value, which allows for invalid responses.

  • Pass a reference to a variable and return any possible error code within that.


I see there are many nice solutions, but I approach it in another way if I have this situation.

auto doSomething()
{        
    // calculations
    return std::make_pair(error_code, value)
}

int main()
{
    auto result = doSomething();
    if (!result.first)
    {
        std::cout << result.second;
    }
    else
    {
        std::cout << "Something went wrong: " << result.second;
    }
}

For me it's a clean solution than passing bool as reference. auto return type deduction is supported from c++14


Return an error handle. Have an error manager keep the error codes and additional informations (e.g. ERROR_INVALID_PARAMETER and name-value-pairs like ParameterName="pszFileName"). This information can be accessed using the handle. The caller can check the error handle against a NO_ERROR_HANDLE. If true, no error occurred. The caller can augment the error information and pass the handle up the stack. The error manager can be one for the process or one for each thread.


I would suggest following:

class foo {
public:
    long doSomething();
    long doSomething(error_code &e);
};

Where error_code is some type that holds error. It may be integer or better something based on boost::system::error_code.

And you supply two functions:

  1. First version throws the error, for example throw boost::system::system_error that is created from boost::system::error_code.
  2. Second returns the error code into e.


The most common practice is to return the error code

long result;
int error = some_obj.SomeMethod(&result);

or return a value that indicate there was an error:

long result = some_obj.SomeMethod();
if (result < 0) error = some_obj.GetError();


In C++17 you use std::optional from the <optional> header:

std::optional<long> myobject = some_func(some_bool);
if (myobject.has_value()) {
    // do stuff with myobject.value()
} else {
    // myobject has no value
}

// ...

// Example function that returns an optional
std::optional<long> some_func(bool b) {
    if (b) 
        return 15;
    return {};
}


define all the error codes in a File. based on error category you can return the error code and the caller can decide what went wrong and caller can return its own error code.

for example

#define FILE_ERROR        1
#define SANITY_ERROR      2

int WriteToFile(char* Data, int iErrorCode)
{
   char* FileName;

  if (!FileOpen(FileName, &iErrorCode))
  {
     //Analyze error code and make decision on what to ignore or terminate
     iErrorCode = FILE_ERROR;
     return 0;
  }

}

int FileOpen(char* FileName, int* iErrorCode)
{

    if (FileName == null)
     {
       iErrorCode = SANITY_ERROR;
      return 0;
    }

    ///// next code blocks
    return 1;
}


I found a new way to do it. It is non-standard and this is an entirely new way to do it. So consider using this approach cautiously.

Use the following header file:

SetError.h:

#include <string> // for string class 



#ifndef SET_ERROR_IS_DEFINED
#define SET_ERROR_IS_DEFINED

class Error {

public:
    int code = 0;
    std::string errorMessage;
    std::string fileName;
    std::string functionName;

    Error() {}

    Error(int _errorCode, std::string _functionName = "", std::string _errorMessage = "", std::string _fileName = "")
    {
        code = _errorCode;
        functionName = _functionName;
        errorMessage = _errorMessage;
        fileName = _fileName;
    }
};

#if defined(_DEBUG) || !defined(NDEBUG) 
#define ___try { _ERROR.code = 0; bool __valid_try_mode_declared; 
#define ___success }
#define SetError(pErrorData) __valid_try_mode_declared = true; _ERROR = *pErrorData; delete pErrorData;
#else
#define ___try { _ERROR.code = 0;
#define ___success }
#define SetError(pErrorData) _ERROR = *pErrorData; delete pErrorData; 
#endif

#endif

inline Error _ERROR;

Include it everyware.

Example of how to use:

Main.cpp:

#include "SetError.h"
#include <iostream>


bool SomeFunction(int value) ___try; 
{ 


    if (value < 0) {
        SetError(new Error(10, "SomeFunction", "Some error", "File main.cpp"));
        return false;
    }

    return true;
} ___success; // You mast to warp the function with both ___try and ___success
// These keywords must be at the start and the end of the function!




int main()
{
    using namespace std;

    bool output = SomeFunction(-1);

    if (_ERROR.code != 0) { // This is how you check the error code. using the global _ERROR object
        cout << "error code: " << _ERROR.code << ", from function: " 
            << _ERROR.functionName << ", from file: " << _ERROR.fileName;
    }

    cout << endl << "Founction returned: " << output << endl;

    return 1;
}

If you have some functions that run in another thread, these functions need to be inside namespace and then you can do this:

namespace FunctionsInSomeThread
{
    #include "SetError.h"

    bool SomeFunc1() ___try;
    {
        SetError(new Error(5, "SomeFunction2", "Some error from another thread", "File main.cpp"))
        return true;
    } ___success;

    bool SomeFunc2() ___try;
    {
        SetError(new Error(5, "SomeFunction2", "Some error from another thread", "File main.cpp"))
            return true;
    } ___success;

}

And to access _Error, you need to add the namespace of the thread

if (FunctionsInSomeThread::_ERROR.code != 0)
{
    // Error handling
}

Or in case it is inside the same namespace then no need to add FunctionsInSomeThread:: before.

The idea behind this is that you can't warp the function only with ___success; keyword. You will get compile error. So the developer will never return old error code from another function.

If you wrote ___success; at the end of the codeblock, you must write also ___try; at the start! You also can't use SetError macro if it is not wrapped in ___try; and ___success;.

The idea come from the AutoIt language where you have this consept: https://www.autoitscript.com/autoit3/docs/functions/SetError.htm

So this is almost the same in C if you use this header.

0

精彩评论

暂无评论...
验证码 换一张
取 消