Какие идиомы следует использовать, если нельзя использовать исключения?
Я пока остановился на вводе метода bool init(..args...),
который проводит инициализацию и возвращает флаг успешности инициализации.
В случае если init() вернул false, объект надо удалить.
class Foo : Base
{
public:
Foo() : rawPtr(NULL) {}
bool init()
{
if (!Base::init("some arg"))
return false;
if (!mem1.init())
return g_log.write("Foo::init : can't init mem1"), false;
mem2.reset(CreateSome());
if (!mem2)
return g_log.write("Foo::init : can't create mem2"), false;
rawPtr = CreateBar();
if (rawPtr == NULL)
return false;
return true;
}
~Foo()
{
if (rawPtr == NULL)
FreeBar(rawPtr);
}
private:
SomeDefaultConstructible mem1;
smart_handle mem2;
Bar* rawPtr;
};
template<class C>
inline C* create()
{
if (C* obj = new(std::nothrow) C())
{
if (obj->init())
{
return obj;
}
delete obj;
}
return NULL;
}
У такого подхода даже есть преимущество над конструкторами — в init() можно вызывать виртуальные методы.
Однако, я не уверен что это действительно хорошее решение, может есть лучше.
Также мне непонятно что делать с объектами создаваемыми на стеке.
Видимо надо дополнительно вводить еще и метод free(), чтобы сразу откатить состояние объекта если init() провалился.
Это сразу же убивает автоматический вызов деструкторов,
к тому же иногда надо будет дополнительно вводить код для предотвращения двойной деинициализации.
Здравствуйте, Abyx, Вы писали:
A>Какие идиомы следует использовать, если нельзя использовать исключения?
A>Я пока остановился на вводе метода bool init(..args...),
приемлемый подход, минусы вы и сами видите
могу посоветовать еще подход bool IsValid()
в обоих случаях надобности в методе free не вижу
A>Видимо надо дополнительно вводить еще и метод free(), чтобы сразу откатить состояние объекта если init() провалился. A>Это сразу же убивает автоматический вызов деструкторов,
Непонятно, зачем — вполне достаточно проверять флаг в самом деструкторе. А почему, если не секрет, исключения исключены (пардон за тавтологию)?
Здравствуйте, Vamp, Вы писали:
A>>Видимо надо дополнительно вводить еще и метод free(), чтобы сразу откатить состояние объекта если init() провалился. A>>Это сразу же убивает автоматический вызов деструкторов, V>Непонятно, зачем — вполне достаточно проверять флаг в самом деструкторе. А почему, если не секрет, исключения исключены (пардон за тавтологию)?
дай угадаю:
— более простой контроль на флоу?
— исключениефобия?
— эмбеддед система, но по сути тоже исключениефобия (см. technical report on c++ performance).
— не поддерживает компилятор?
I>- более простой контроль на флоу? I>- исключениефобия?
Я сам не большой фанат исключений. Исключения нужны там, где они нужны. Скажем так, я считаю, в программе должен быть ровно один блок try/catch — в мейн. Все, что бросило исключение — проваливается в самый низ, логгируется и программа завершается.
Если программа может продолжаться — то это не исключение, а возврат ошибки из функции.
Но если объект критически важен, и его несоздание должно вызывать исключение — то надо вызвать исключение, я так считаю.
Здравствуйте, Vamp, Вы писали:
A>>Видимо надо дополнительно вводить еще и метод free(), чтобы сразу откатить состояние объекта если init() провалился. A>>Это сразу же убивает автоматический вызов деструкторов, V>Непонятно, зачем — вполне достаточно проверять флаг в самом деструкторе.
но как выяснили такой код всегда можно преобразовать к виду
Foo foo;
if (!foo.init()) return;
V>А почему, если не секрет, исключения исключены (пардон за тавтологию)?
потому что проект собирается с -fno-exceptions
наверное потому что проект для embedded, а исключения вроде как раздувают бинарник (таблицами)
на самом деле я не знаю, просто такое требование %)
Вот в llvm например тоже запрещены исключения, потому что ллвм это библиотека, и разработчики не хотят чтобы использование ллвм мешало компилить код с -fno-exceptions, потому что "you only pay for what you use". Так что разные могут быть причины.
Здравствуйте, Vamp, Вы писали:
I>>- более простой контроль на флоу? I>>- исключениефобия? V>Я сам не большой фанат исключений. Исключения нужны там, где они нужны. Скажем так, я считаю, в программе должен быть ровно один блок try/catch — в мейн. Все, что бросило исключение — проваливается в самый низ, логгируется и программа завершается. V>Если программа может продолжаться — то это не исключение, а возврат ошибки из функции. V>Но если объект критически важен, и его несоздание должно вызывать исключение — то надо вызвать исключение, я так считаю.
Здравствуйте, ilnar, Вы писали:
I>признаюсь, я тоже не фанат. но готовить умею)
а я фанат
ненавижу if-ы, они увеличивают цикломатическое число и негавтивно влияют на ковередж (юнит-тестами замучаешься все decisions покрывать)
хорошо, когда if находится в классе, а не у каждого клиента этого класса
Здравствуйте, Vamp, Вы писали:
I>>- более простой контроль на флоу? I>>- исключениефобия? V>Я сам не большой фанат исключений. Исключения нужны там, где они нужны. Скажем так, я считаю, в программе должен быть ровно один блок try/catch — в мейн. Все, что бросило исключение — проваливается в самый низ, логгируется и программа завершается. V>Если программа может продолжаться — то это не исключение, а возврат ошибки из функции. V>Но если объект критически важен, и его несоздание должно вызывать исключение — то надо вызвать исключение, я так считаю.
исключения нужны для возврата ошибки из конструктора
struct Foo
{
Foo()
: bar() // may throw
, baz() // may throw
{}
Bar bar;
Baz baz;
};
и для возврата ошибки из функции которая иначе не может вернуть ошибку
int x = lexical_cast<int>("...");
в обоих случая варианты без исключений существенно снижают читаемость и надежность кода — исключения приходится заменять на член класса (isValid, whatHappened) или глобальную thread-local переменную.
Здравствуйте, Abyx, Вы писали:
A>Здравствуйте, Vamp, Вы писали:
I>>>- более простой контроль на флоу? I>>>- исключениефобия? V>>Я сам не большой фанат исключений. Исключения нужны там, где они нужны. Скажем так, я считаю, в программе должен быть ровно один блок try/catch — в мейн. Все, что бросило исключение — проваливается в самый низ, логгируется и программа завершается. V>>Если программа может продолжаться — то это не исключение, а возврат ошибки из функции. V>>Но если объект критически важен, и его несоздание должно вызывать исключение — то надо вызвать исключение, я так считаю.
A>исключения нужны для возврата ошибки из конструктора
Vamp просто не фанат, но "с творчеством знаком", будь уверен
Здравствуйте, Abyx, Вы писали:
A>Здравствуйте, Vamp, Вы писали:
I>>>- более простой контроль на флоу? I>>>- исключениефобия? V>>Я сам не большой фанат исключений. Исключения нужны там, где они нужны. Скажем так, я считаю, в программе должен быть ровно один блок try/catch — в мейн. Все, что бросило исключение — проваливается в самый низ, логгируется и программа завершается. V>>Если программа может продолжаться — то это не исключение, а возврат ошибки из функции. V>>Но если объект критически важен, и его несоздание должно вызывать исключение — то надо вызвать исключение, я так считаю.
A>исключения нужны для возврата ошибки из конструктора
овпрос был в другом, почему не хотите исключений?
Здравствуйте, Vamp, Вы писали:
I>>- более простой контроль на флоу? I>>- исключениефобия? V>Я сам не большой фанат исключений. Исключения нужны там, где они нужны. Скажем так, я считаю, в программе должен быть ровно один блок try/catch — в мейн. Все, что бросило исключение — проваливается в самый низ, логгируется и программа завершается. V>Если программа может продолжаться — то это не исключение, а возврат ошибки из функции. V>Но если объект критически важен, и его несоздание должно вызывать исключение — то надо вызвать исключение, я так считаю.
это да. на плюсах нет возможности корректно обработать исключение. скажем, функция foo выполнила кучу операций и в какой-то момент решила открыть файл bar. и тут выяснилось, что файл открыть невозможно. что делать? кидем исключение. ловим его и видим, что (допустим) юзер вытащил флешку или cd/dvd. вот было бы здорово сказать юзеру -- воткни флешку обратно и продолжить выполнние функции foo с того места, в котором было брошено исключение. но увы... это невозможно (если только не реализовать свой диспетчер исключений).
americans fought a war for a freedom. another one to end slavery. so, what do some of them choose to do with their freedom? become slaves.
On 28.07.2011 22:06, Abyx wrote:
> У такого подхода даже есть преимущество над конструкторами — в init() можно > вызывать виртуальные методы.
Преимуществ нет. В конструкторе тоже можно вызывать виртуальные методы.
Лучше поборись за использование исключений.
> > Однако, я не уверен что это действительно хорошее решение, может есть лучше.
Использовать исключения.
> Также мне непонятно что делать с объектами создаваемыми на стеке. > Видимо надо дополнительно вводить еще и метод free(), чтобы сразу откатить > состояние объекта если init() провалился.
Ничего окромя
if( doAll() != doneWell )
return;
не придумаешь. И именно с этим борятся исключения.
> Это сразу же убивает автоматический вызов деструкторов, > к тому же иногда надо будет дополнительно вводить код для предотвращения двойной > деинициализации.
ага, тогда давай на С сразу пиши. Нафига тебе С++ ?
Без иключений нельзя использовать STL, потому что оно их кидает.
Любая НОРМАЛЬНАЯ библиотека будет тоже на исключениях.
Так что тебе останется только С-шные либы исползовать,
и свой протокол инициализации и деструкторизации придумывать.
On 28.07.2011 22:31, Vamp wrote:
> Я сам не большой фанат исключений. Исключения нужны там, где они нужны. Скажем > так, я считаю, в программе должен быть ровно один блок try/catch — в мейн. Все,
Ну, почти правильно думашь, и почти так оно и должно быть в ХОРОШЕЙ программе
на С++.
> что бросило исключение — проваливается в самый низ, логгируется и программа > завершается.
Ну, если конечно нельзя восстановиться и продолжить работу.
> Если программа может продолжаться — то это не исключение, а возврат ошибки из > функции.
Нет. Путаешь. Если прогамма может продолжить работу, выбрав и уничтожив
стек вызовов и всё, что там есть, -- это одно. А если ПОТОК ВЫПОЛНЕНИЯ программы
не может течь далее в рамках его нормальных ветвей (а других в общем и не
бывает), то это вот как раз то самое место, где кидается исключение.
В общем случае это совершенно разные вещи.
Ты всегда можешь сказать программе: "А давай-ка попробуем сделать вот это,
вдруг получится", и если не получится, то привести программу в состояние,
как будто бы ничего и не было.
> Но если объект критически важен, и его несоздание должно вызывать исключение — > то надо вызвать исключение, я так считаю.
On 28.07.2011 22:45, uzhas wrote:
> а я фанат > ненавижу if-ы, они увеличивают цикломатическое число и негавтивно влияют на > ковередж (юнит-тестами замучаешься все decisions покрывать) > хорошо, когда if находится в классе, а не у каждого клиента этого класса
Главное даже не if-ы, а корректность программы.
Вот протокол (сигнатура) метода:
class Complex
{
...
Complex divide( const Complex& r ) const;
...
};
Ну и что ему возвращать при таком вызове:
Complex res = Complex( 2, 3 ).divide( 0 );
?
Если что-то возвращать, программа просто тупо становится
некорректной. А если вводить "флаги успешности выполнения
операции" -- сразу становится неуклюжей и неестественной.
Здравствуйте, мыщъх, Вы писали:
М>Здравствуйте, Vamp, Вы писали:
I>>>- более простой контроль на флоу? I>>>- исключениефобия? V>>Я сам не большой фанат исключений. Исключения нужны там, где они нужны. Скажем так, я считаю, в программе должен быть ровно один блок try/catch — в мейн. Все, что бросило исключение — проваливается в самый низ, логгируется и программа завершается. V>>Если программа может продолжаться — то это не исключение, а возврат ошибки из функции. V>>Но если объект критически важен, и его несоздание должно вызывать исключение — то надо вызвать исключение, я так считаю.
М>это да. на плюсах нет возможности корректно обработать исключение. скажем, функция foo выполнила кучу операций и в какой-то момент решила открыть файл bar. и тут выяснилось, что файл открыть невозможно. что делать? кидем исключение. ловим его и видим, что (допустим) юзер вытащил флешку или cd/dvd. вот было бы здорово сказать юзеру -- воткни флешку обратно и продолжить выполнние функции foo с того места, в котором было брошено исключение. но увы... это невозможно (если только не реализовать свой диспетчер исключений).
Например можно сделать без всяких диспетчеров так:
enum Stage
{
Start,
Step1,
Step2,
Step3,
Finish
};
struct stage_error: std::runtime_error
{
stage_error(const std::string &message, Stage stage):
std::runtime_error(message),
last_stage(stage)
{ }
Stage last_stage;
}
void foo(..., Stage current = Start)
{
// на основе значение current выполняем определённые действия и меняем current на значение следующей стадии
// ...
// в текущей стадии ошибка!throw stage_error("ошибка", current);
// ...
}
// здесь мы вызываем обработку
{
try
{
foo(...);
}
catch (const stage_error &ex)
{
// выводим сообщение об ошибке
// ...(ex.what())
// просим пользователя одуматься :)
// вызываем обработку с того самого места
foo(..., ex.last_stage);
}
}
Я это написал не для того чтобы поучаствовать в споре "исключения vs коды возврата", а для того чтобы показать что такую задачу можно решить с помощью исключений. Более того, я хочу сказать что в данном конкретном случае механизм обработки ошибок не существенен. В любом случае функция foo должна уметь возобновлять свою работу с определённого места и без венсения изменений в функцию foo решить такую задачу невозможно.
ООП на гольном С:
любая функция возвращает код ошибки, а первым параметром принимает this — указатель на структуру.
И получается что-то типа такого:
#define CHECK(x) \
do {\
int retcode = (x);\
if (retcode != OK)\
return retcode;\
} while(0)
typedef struct {
int value;
} my_class;
int my_class_init(my_class* this, int value) {
this->value = value;
return OK;
}
int my_class_serialize(my_class* this, char* str, size_t size) {
if ( snprintf(....
...
}
int foo(char* str, size_t size) {
my_class my_class_inst;
CHECK(my_class_init(&my_class_inst, 10));
CHECK(my_class_serialize(&my_class_inst, str, size));
return OK;
}
только вот RAII нет — нужно по идее собирать списки деинициализации и в CHECK чистить все это дело
в общем, закат солнца вручную.
не знаю кому это сегодня нужно
кроме стрых проектов, написанных так для совместимости с разными страшными компиляторами
Здравствуйте, MasterZiv, Вы писали:
MZ>On 28.07.2011 22:45, uzhas wrote:
>> а я фанат >> ненавижу if-ы, они увеличивают цикломатическое число и негавтивно влияют на >> ковередж (юнит-тестами замучаешься все decisions покрывать) >> хорошо, когда if находится в классе, а не у каждого клиента этого класса
MZ>Главное даже не if-ы, а корректность программы.
MZ>Вот протокол (сигнатура) метода:
MZ>class Complex MZ>{ MZ> ... MZ> Complex divide( const Complex& r ) const; MZ> ... MZ>};
MZ>Ну и что ему возвращать при таком вызове:
MZ>Complex res = Complex( 2, 3 ).divide( 0 );
MZ>?
MZ>Если что-то возвращать, программа просто тупо становится MZ>некорректной. А если вводить "флаги успешности выполнения MZ>операции" -- сразу становится неуклюжей и неестественной.