Ответ 1
Вы можете передать переменную как ссылочный и возвращаемый код ошибки.
Я использую коды ошибок для обработки ошибок в моем проекте С++. Проблема заключается в том, как вернуть коды ошибок из функции, которая должна возвращать некоторую переменную/объект.
рассмотрим это:
long val = myobject.doSomething();
Здесь myobject является объектом некоторого класса. Если функция doSomething встречает некоторое условие ошибки, то как она должна уведомить вызывающего абонента (без использования исключений).
Возможные решения:
Теперь, как я могу уведомить вызывающего абонента о некотором состоянии ошибки?
Вы можете передать переменную как ссылочный и возвращаемый код ошибки.
Создайте шаблон, называемый, скажем, 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
. Вам также нужно будет выполнить некоторую настройку производительности, чтобы избежать неприятных накладных расходов. Если вы хотите сделать его более гибким, вы можете параметризовать его как по типу возвращаемого значения, так и по типу ошибки. (Это лишь незначительное увеличение сложности.)
Вероятно, вам нужно что-то вроде Alexandresu Ожидаемое <T> идиома.
Обычно возвращается код возврата/ошибки и предоставляет доступ к свойству или элементу с результатами.
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...
}
У вас есть три варианта:
Создайте класс, содержащий возвращаемое значение и возможный код ошибки.
Используйте что-то вроде boost::optional
для возвращаемого значения, что допускает недопустимые ответы.
Передайте ссылку на переменную и верните в нее любой возможный код ошибки.
Возвращает дескриптор ошибки. У диспетчера ошибок сохраняются коды ошибок и дополнительные сведения (например, ERROR_INVALID_PARAMETER
и пар имя-значение, например ParameterName="pszFileName"
). К этой информации можно обращаться с помощью дескриптора. Вызывающий может проверить дескриптор ошибки на NO_ERROR_HANDLE. Если значение true, ошибки не было. Вызывающий может увеличить информацию об ошибке и передать дескриптор в стек.
Менеджер ошибок может быть один для процесса или по одному для каждого потока.
Наиболее распространенной практикой является возврат кода ошибки
long result;
int error = some_obj.SomeMethod(&result);
или вернуть значение, указывающее на наличие ошибки:
long result = some_obj.SomeMethod();
if (result < 0) error = some_obj.GetError();
Вы можете использовать исключения.
Я бы предложил следующее:
class foo {
public:
long doSomething();
long doSomething(error_code &e);
};
Где error_code
- это некоторый тип, который содержит ошибку. Это может быть целое или лучшее что-то, основанное на boost::system::error_code
.
И вы предоставляете две функции:
boost::system::system_error
, созданный из boost::system::error_code
.Вы можете вернуть std::pair
, содержащий как код ошибки (или объект ошибки), так и желаемый объект возврата. Объекту интереса нужен конструктор или значение по умолчанию, чтобы вы могли возвращать что-то даже при возникновении ошибки.
определить все коды ошибок в файле. на основе категории ошибок вы можете вернуть код ошибки, и вызывающий может решить, что пошло не так, и вызывающий может вернуть свой собственный код ошибки.
например
#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;
}
Я вижу, что есть много хороших решений, но я подхожу к этому по-другому, если у меня такая ситуация.
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
Я нашел новый способ сделать это. Это нестандартно, и это совершенно новый способ сделать это. Поэтому подумайте об использовании этого подхода осторожно.
Используйте следующий заголовочный файл:
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, если вы используете этот заголовок.