Я перепробовал все альтернативы исключениям, которые мог найти (переменная ошибки члена, даже setjmp / longjmp), и все они по-своему отстойно. Очень необычный шаблон, который мне очень понравился, - это передача ссылки на объект ошибки и проверка того, находится ли ошибка как самая первая операция в любой функции:
int function1(Error& e, char * arg)
{
if(e.failure())
return -1; // a legal, but invalid value
// ...
}
int function2(Error& e, int arg)
{
if(e.failure())
return -1; // a legal, but invalid value
// ...
}
int function3(Error& e, char * arg)
{
if(e.failure())
return -1;
// if function1 fails:
// * function2 will ignore the invalid value returned
// * the error will cascade, making function2 "fail" as well
// * the error will cascade, making function3 "fail" as well
return function2(e, function1(e, arg));
}
С некоторыми работами это относится и к конструкторам:
class Base1
{
protected:
Base1(Error& e)
{
if(e.failure())
return;
// ...
}
// ...
};
class Base2
{
protected:
Base2(Error& e)
{
if(e.failure())
return;
// ...
}
// ...
};
class Derived: public Base1, public Base2
{
public:
Derived(Error& e): Base1(e), Base2(e)
{
if(e.failure())
return;
...
}
};
Основная проблема заключается в том, что вы не получаете автоматическое удаление в случае сбоя конструктора динамически размещенного объекта. Обычно я заключаю вызовы new в функцию, подобную этой:
// yes, of course we need to wrap operator new too
void * operator new(Error& e, size_t n)
{
if(e.failure())
return NULL;
void * p = ::operator new(n, std::nothrow_t());
if(p == NULL)
/* set e to "out of memory" error */;
return p;
}
template<class T> T * guard_new(Error& e, T * p)
{
if(e.failure())
{
delete p;
return NULL;
}
return p;
}
Что будет использоваться так:
Derived o = guard_new(e, new(e) Derived(e));
Преимущества этой техники включают в себя:
- совместимость с C (если класс Error объявлен надлежащим образом)
- потокобезопасный
- накладные расходы нулевого размера в классах
- Класс ошибок может быть на 100% непрозрачным; с помощью макросов для доступа, объявления и передачи он может включать в себя все виды информации, включая, но не ограничиваясь, исходный файл и строку, имя функции, обратный след стека и т. д.
- это относится к довольно сложным выражениям, во многих случаях делая его почти как исключения