Правильный подход для возврата кодов ошибок в С++

Я использую коды ошибок для обработки ошибок в моем проекте С++. Проблема заключается в том, как вернуть коды ошибок из функции, которая должна возвращать некоторую переменную/объект.

рассмотрим это:

long val = myobject.doSomething();

Здесь myobject является объектом некоторого класса. Если функция doSomething встречает некоторое условие ошибки, то как она должна уведомить вызывающего абонента (без использования исключений).

Возможные решения:

  • Имейте член данных (скажем err_) в классе, который может быть проверен вызывающим. Но это было бы небезопасно в многопоточном приложении, использующем один и тот же объект и вызывающем ту же функцию.
  • Используйте некоторую глобальную переменную ошибки, опять же такую ​​же проблему в многопоточной среде.

Теперь, как я могу уведомить вызывающего абонента о некотором состоянии ошибки?

Ответ 1

Вы можете передать переменную как ссылочный и возвращаемый код ошибки.

Ответ 2

Создайте шаблон, называемый, скажем, Maybe, который он параметризован вашим типом возвращаемого значения. Всякий раз, когда вы возвращаете значение, оберните его в этот шаблон следующим образом:

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

Шаблон Maybe имеет способ создания экземпляра с кодом ошибки (возможно, статическим методом):

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

Но обычно будет просто возвращено со значением:

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

(Разумеется, вам придется переопределить соответствующие конструкторы, операторы присваивания и т.д.)

На стороне клиента вы вызываете метод для проверки ошибки.

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

Снова вам понадобятся соответствующие операторы, определенные для использования Maybe<long> везде, где требуется long.

Это звучит как много работы, но на самом деле работа выполняется раз в создании хорошего пуленепробиваемого шаблона Maybe. Вам также нужно будет выполнить некоторую настройку производительности, чтобы избежать неприятных накладных расходов. Если вы хотите сделать его более гибким, вы можете параметризовать его как по типу возвращаемого значения, так и по типу ошибки. (Это лишь незначительное увеличение сложности.)

Ответ 4

Обычно возвращается код возврата/ошибки и предоставляет доступ к свойству или элементу с результатами.

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

Также часто передается указатель, который установлен на возвращаемое значение, и возвращает код возврата/ошибки. (См. HrQueryAllRows).

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

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

Ответ 5

У вас есть три варианта:

  • Создайте класс, содержащий возвращаемое значение и возможный код ошибки.

  • Используйте что-то вроде boost::optional для возвращаемого значения, что допускает недопустимые ответы.

  • Передайте ссылку на переменную и верните в нее любой возможный код ошибки.

Ответ 6

Возвращает дескриптор ошибки. У диспетчера ошибок сохраняются коды ошибок и дополнительные сведения (например, ERROR_INVALID_PARAMETER и пар имя-значение, например ParameterName="pszFileName"). К этой информации можно обращаться с помощью дескриптора. Вызывающий может проверить дескриптор ошибки на NO_ERROR_HANDLE. Если значение true, ошибки не было. Вызывающий может увеличить информацию об ошибке и передать дескриптор в стек. Менеджер ошибок может быть один для процесса или по одному для каждого потока.

Ответ 7

Наиболее распространенной практикой является возврат кода ошибки

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

или вернуть значение, указывающее на наличие ошибки:

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

Ответ 8

Вы можете использовать исключения.

Ответ 9

Я бы предложил следующее:

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

Где error_code - это некоторый тип, который содержит ошибку. Это может быть целое или лучшее что-то, основанное на boost::system::error_code.

И вы предоставляете две функции:

  • Первая версия вызывает ошибку, например throw boost::system::system_error, созданный из boost::system::error_code.
  • Второй возвращает код ошибки в e.

Ответ 10

Вы можете вернуть std::pair, содержащий как код ошибки (или объект ошибки), так и желаемый объект возврата. Объекту интереса нужен конструктор или значение по умолчанию, чтобы вы могли возвращать что-то даже при возникновении ошибки.

Ответ 11

определить все коды ошибок в файле. на основе категории ошибок вы можете вернуть код ошибки, и вызывающий может решить, что пошло не так, и вызывающий может вернуть свой собственный код ошибки.

например

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

Ответ 12

Я вижу, что есть много хороших решений, но я подхожу к этому по-другому, если у меня такая ситуация.

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

Для меня это более чистое решение, чем прохождение bool в качестве эталона. auto возвращение типа возврата поддерживается с С++ 14

Ответ 13

Я нашел новый способ сделать это. Это нестандартно, и это совершенно новый способ сделать это. Поэтому подумайте об использовании этого подхода осторожно.

Используйте следующий заголовочный файл:

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;

Включите все.

Пример использования:

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

Если у вас есть некоторые функции, которые выполняются в другом потоке, эти функции должны находиться внутри пространства имен, и тогда вы можете сделать это:

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;

}

И чтобы получить доступ к _Error, вам нужно добавить пространство имен потока

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

Или, если он находится в том же пространстве имен, нет необходимости добавлять FunctionsInSomeThread :: before.

Идея заключается в том, что вы не можете деформировать функцию только с помощью ___success; ключевое слово. Вы получите ошибку компиляции. Поэтому разработчик никогда не вернет старый код ошибки из другой функции.

Если вы написали ___success; в конце кодового блока вы также должны написать ___try; в начале! Вы также не можете использовать макрос SetError если он не заключен в ___try; и ___success; ,

Идея пришла от языка AutoIt, где у вас есть эта концепция: https://www.autoitscript.com/autoit3/docs/functions/SetError.htm

Так что это почти то же самое в C, если вы используете этот заголовок.