Какие идиомы следует использовать, если нельзя использовать исключения?
Я пока остановился на вводе метода 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>операции" -- сразу становится неуклюжей и неестественной.
Здравствуйте, igna, Вы писали:
I>Здравствуйте, MasterZiv, Вы писали:
MZ>>Без иключений нельзя использовать STL, потому что оно их кидает.
I>Какие исключения кидает STL?
I>(Именно STL, а не Standard Library; и именно сама STL, а не пользовательские функции, вызываемые из STL.)
Здравствуйте, sts, Вы писали:
sts>как минимум operator new который stl использует и который по умолчанию не является пользовательской функцией может кидать исключение
А пользовательским аллокатором нельзя это запретить? Если можно, кидает ли STL еще какие исключения? То есть, действительно ли "Без иключений нельзя использовать STL".
Здравствуйте, igna, Вы писали:
I>А пользовательским аллокатором нельзя это запретить?
А как? Даже если он не стрельнет исключением и вернёт, скажем, 0, как заставить тот же vector не пройтись по памяти в попытке инициализации значений?
I>Если можно, кидает ли STL еще какие исключения?
Кидает, но к сути вопроса это относится не будет (std::vector::at бросает std::out_of_range — метод можно просто не использовать).
Здравствуйте, igna, Вы писали:
I>Здравствуйте, sts, Вы писали:
sts>>как минимум operator new который stl использует и который по умолчанию не является пользовательской функцией может кидать исключение
I>А пользовательским аллокатором нельзя это запретить? Если можно, кидает ли STL еще какие исключения? То есть, действительно ли "Без иключений нельзя использовать STL".
а как сообщать об ошибках, что памяти нет ?
все равно интерфейс stl рассчитан на исключения.
иначе придется все время какой--нибудь свой собственный errno проверять.
тогда уж проще исполльзовать c-style контейнеры (и все остальное тоже) которые будут возвращать код ошибки.
Здравствуйте, MasterZiv, Вы писали:
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>?
Ну и что вы будете делать с исключением, которое можно здесь бросить ?
( Чем такое поведение принципиально будет отличатся от exit(some_code)? )
Здравствуйте, wander, Вы писали:
W>Здравствуйте, igna, Вы писали:
I>>(Именно STL, а не Standard Library; и именно сама STL, а не пользовательские функции, вызываемые из STL.)
W>Конструктор std::string в GCC бросает исключение, если передать туда ноль.
Здравствуйте, sts, Вы писали:
sts>Здравствуйте, wander, Вы писали:
W>>Здравствуйте, igna, Вы писали:
I>>>(Именно STL, а не Standard Library; и именно сама STL, а не пользовательские функции, вызываемые из STL.)
W>>Конструктор std::string в GCC бросает исключение, если передать туда ноль.
sts>как я понимаю, это не стандартное поведение
втом смысле, что не все так делают
некоторые могут падать
Здравствуйте, sts, Вы писали:
sts>Здравствуйте, wander, Вы писали:
W>>Здравствуйте, igna, Вы писали:
I>>>(Именно STL, а не Standard Library; и именно сама STL, а не пользовательские функции, вызываемые из STL.)
W>>Конструктор std::string в GCC бросает исключение, если передать туда ноль.
sts>как я понимаю, это не стандартное поведение
Да. Но тем не менее.
К тому же не любой код кросскомпиляторный. Есть проекты, которые живут только на MSVC или GCC и используют их языковые расширения и особенности.
Здравствуйте, MasterZiv, Вы писали: MZ>Ну и что ему возвращать при таком вызове: MZ>Complex res = Complex( 2, 3 ).divide( 0 ); MZ>? MZ>Если что-то возвращать, программа просто тупо становится MZ>некорректной. А если вводить "флаги успешности выполнения MZ>операции" -- сразу становится неуклюжей и неестественной.
Ну, в данном конкретном случае есть INF и NaN. А в общем же случае заводят внутренние флаги корректности/ошибки, которые необходимо проверять внутри при каждой операции (чтобы всё окончательно не развалилось). И публичный метод доступа, который пользователю нужно проверять время от времени. Пример есть в стандартной библиотеке C++ — iostreams.
Здравствуйте, B0FEE664, Вы писали:
MZ>>Ну и что ему возвращать при таком вызове: MZ>>Complex res = Complex( 2, 3 ).divide( 0 ); MZ>>?
BFE>Ну и что вы будете делать с исключением, которое можно здесь бросить ? BFE>( Чем такое поведение принципиально будет отличатся от exit(some_code)? )
Зависит от приложения. В REPL-интерпретаторе калькулятора/ЯП достаточно распечатать "Division by zero error at bla-bla-bla..." и вернуться к циклу read-eval-print (конечно, вместо конкретных чисел — переменные).
Здравствуйте, B0FEE664, Вы писали:
BFE>Ну и что вы будете делать с исключением, которое можно здесь бросить ?
поймаю его на границе слоя, и сообщу пользователю что операция не удалась
например
— пользователь жмет на кнопку, программа пытается выполнить операцию — возникает исключение, пользователь получает уведомление что операция не удалась — пользователь может изменить входные данные \ нажать на другую кнопку
— на веб-сервис приходит запрос — ... — сервис возвращает "500 internal error" и продолжает работу
— исключение ловится в catch(any_exception) в main loop, молча выводится в лог, делается minidump. потом можно проанализировать ситуацию и пофиксить баг.
Здравствуйте, MasterZiv, Вы писали:
MZ>Преимуществ нет. В конструкторе тоже можно вызывать виртуальные методы.
полиморфно — нельзя.
>> Однако, я не уверен что это действительно хорошее решение, может есть лучше. MZ>Использовать исключения.
сказано "без исключений" — значит *без исключений* .
постарайтесь обойтись без высказываний типа "сабж не нужен".
не знаете как решить проблему — лучше помолчите.
MZ>ага, тогда давай на С сразу пиши. Нафига тебе С++ ? MZ>Без иключений нельзя использовать STL, потому что оно их кидает. MZ>Любая НОРМАЛЬНАЯ библиотека будет тоже на исключениях.
см. LLVM
class Foo
{
public:
static Foo* create(..args..)
{
scoped_ptr<Bar> bar(Bar::create());
if (!bar) return;
scoped_handle h(CreateSome(...));
if (!h) return;
return new(std::nothrow) Foo(bar.release(), h.release());
}
Foo(Bar* bar, HANDLE h) : bar(bar), h(h) {}
private:
scoped_ptr<Bar> bar;
scoped_handle h;
};
Преимущества
— нет отдельно висящего метода init(), create выдает гарантированно валидный настроенный объект, либо NULL
— ???
Недостатки
— чтобы создать объект на стеке, надо скопипастить всё содержимое create() кроме new
— в конструктор вытаскиваются *все* члены класса с нетривиальной инициализацией — а это нарушает инкапсуляцию
— непонятно как быть с базовым классом
Или — классическая фабрика — функция или (виртуальный) метод
class Foo
{
public:
Foo(Bar* bar, HANDLE h) : bar(bar), h(h) {}
private:
scoped_ptr<Bar> bar;
scoped_handle h;
};
Foo* createFoo(...args..)
{
scoped_ptr<Bar> bar(Bar::create());
if (!bar) return;
scoped_handle h(CreateSome(...));
if (!h) return;
return new(std::nothrow) Foo(bar.release(), h.release());
}
Преимущества — труъ ООП, dependency injection, etc
В остальном всё так же как и у статического метода
Нe только в main — на границах модулей тоже лучше преобразовывать исключения в коды ошибок — при проблемах часть функционала отвалится, но хоть остальное похромает дальше, если сможет. Более чем актуально, когда модули самые что ни на есть модульные, и создаются разными компиляторами.
Здравствуйте, gegMOPO4, Вы писали:
MOP>Ну, в данном конкретном случае есть INF и NaN. А в общем же случае заводят внутренние флаги корректности/ошибки, которые необходимо проверять внутри при каждой операции (чтобы всё окончательно не развалилось). И публичный метод доступа, который пользователю нужно проверять время от времени. Пример есть в стандартной библиотеке C++ — iostreams.
iostreams — дерьмовая вообщем-то библиотека.
Проверять корректность каждой операции в общем случае значит умножать кол-во кода в 3-10 раз, плюс ошибки на неизбежном в таком случае копипасте. Я чтою на позиции, что внутри модудей — исключения, на границах — код ошибки + доп.нформация о ней, в main — все коды ошибок опять же преобразовываются в исключения, и выводим что поймаем
Здравствуйте, Abyx, Вы писали:
A>в обоих случая варианты без исключений существенно снижают читаемость и надежность кода (1)- исключения приходится заменять на член класса (isValid, whatHappened) или глобальную thread-local переменную (2),
которые можно без проблем проигнорировать на любом уровне и получит проблемы на абсолютно другом уровне (к п2).
По п. 1 Сами по себе не снижают, либо снижается надежность игнором проверки ошибок, либо читаемость раздуванием кода в несколько раз за счет этих проверок.
Здравствуйте, мыщъх, Вы писали:
М>это да. на плюсах нет возможности корректно обработать исключение. скажем, функция foo выполнила кучу операций и в какой-то момент решила открыть файл bar. и тут выяснилось, что файл открыть невозможно. что делать? кидем исключение. ловим его и видим, что (допустим) юзер вытащил флешку или cd/dvd. вот было бы здорово сказать юзеру -- воткни флешку обратно и продолжить выполнние функции foo с того места, в котором было брошено исключение. но увы... это невозможно (если только не реализовать свой диспетчер исключений).
Ты говоришь о "семантике возобновления", которой действительно нет в C++. Но это не "корректная обработка исключений".
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Здравствуйте, igna, Вы писали:
MZ>>Без иключений нельзя использовать STL, потому что оно их кидает.
I>Какие исключения кидает STL?
I>(Именно STL, а не Standard Library; и именно сама STL, а не пользовательские функции, вызываемые из STL.)
1) А чем отличается STL от Standard Library?
2) bad_alloc, или что там кидается при отсутствии памяти?
3) Таки да, но кто же использует кастомные аллокаторы? Это чуть ли не весь клиентский код переписывать, раз, во-вторых — как все таки возвращать ошибку аллокатора, который вызван не явно?
Придумайте std::string, который не кидает исключений, и который не надо перед каждой операцией проверять на консистентность.
On 29.07.2011 18:22, B0FEE664 wrote:
> Ну и что вы будете делать с исключением, которое можно здесь бросить ?
Ловить в main или в главном цикле обработки кусков информации, которые
программа обрабатывает. После каждой итерации.
> > ( Чем такое поведение принципиально будет отличатся от exit(some_code)? )
Тем, что не надо В КАЖДОЙ ФУНКЦИИ обрабатывать эту сраную ошибку.
On 29.07.2011 17:03, igna wrote:
> Какие исключения кидает STL? > > (Именно STL, а не Standard Library; и именно сама STL, а не пользовательские > функции, вызываемые из STL.)
On 29.07.2011 21:24, Abyx wrote:
> MZ>Преимуществ нет. В конструкторе тоже можно вызывать виртуальные методы. > полиморфно — нельзя.
Можно. Просто вызовится не тот метод, который ты бы хотел вызвать.
Потому что в момент работы конструктора класса C объект является
объектом класса C, а не объектом класса его наследника.
> сказано "без исключений" — значит *без исключений* . > постарайтесь обойтись без высказываний типа "сабж не нужен". > не знаете как решить проблему — лучше помолчите.
Так я и хочу сказать как раз, что проблема не решается.
Хочешь жить неудобно, -- живи без исключений, имей кучу веток
в программе и всё такое.
Хочешь удобно -- исключения.
Здравствуйте, Abyx, Вы писали:
A>Какие идиомы следует использовать, если нельзя использовать исключения? A>Я пока остановился на вводе метода bool init(..args...), A>который проводит инициализацию и возвращает флаг успешности инициализации. A>В случае если init() вернул false, объект надо удалить.
Да, чем-то подобным постоянно пользуюсь. Не люблю исключения — с ними получается так, что в программе возникает как-бы еще один "скрытый", неочевидный слой выполнения. Вызываешь функцию — и нет никакой гарантии, что не вывалишься куда-нибудь в совершенно другое место. Т.е. если использовать исключения — то нужно их ВЕЗДЕ использовать, кажый чих заворачивать в try/catch, перед использованием каждой функции думать, а какие там она исключения может выбросить, и т.д. Даже при отладке ставить точки останова нужно не только на строку после функции, но и на все catch, которые могут перехватывать исключения. Короче, goto 2.0
А если не использовать исключения — нужно просто сделать, чтобы функции возвращали код возврата, и все. Ну и не забывать писать проверки, но это у меня как-то на автомате получается уже
А есть еще исключения из конструкторов — любимая тема на всяких собеседованиях. С этим у меня вообще все просто: любой объект любого класса имеет как минимум два состояния — НУЛЕВОЕ и инициализированное (рабочее). Нулевое — это состояние по умолчанию, и конструктор просто обнуляет поля класса нужным образом. Никаких исключений там не может быть в принципе И никаких ресурсов в конструкторе никогда не выделяю. Ни в коем случае не открываю в конструкторе файлы, не коннекчусь к сети, не делаю запросы к БД, не выделяю память и т.д.
Ну а дальше — функции типа Init(), Create(), Open(), Load(), Connect() и т.д. по смыслу — уже осуществляющие конкретные реальные действия по переводу объекта из нулевого состояния в рабочее.
Всегда есть функция типа Free(), она освобождает ресурсы если они не освобождены, и обнуляет переменные. Ее вызов ставлю на всякий случай в деструкторе. Когда полей много, делаю еще приватную Clear(), которая как раз тупо обнуляет все поля, и ее вызываю из конструктора и из Free().
С объектами на стеке никаких проблем нет, все это прекрасно работает и на стеке.
мыщъх: М>скажем, функция foo выполнила кучу операций и в какой-то момент решила открыть файл bar. и тут выяснилось, что файл открыть невозможно. что делать? кидем исключение. ловим его и видим, что (допустим) юзер вытащил флешку или cd/dvd. вот было бы здорово сказать юзеру -- воткни флешку обратно и продолжить выполнние функции foo с того места, в котором было брошено исключение. но увы... это невозможно (если только не реализовать свой диспетчер исключений).
Если в целевой директории создать директорию с именем test.txt, то попытка открыть для записи файл с таким же именем приведёт к ошибке доступа. Пользователь может либо удалить файл и попробовать продолжить выполнение X::foo, либо отметить дальнейшее выполнение X::foo. Пример собирается в MinGW g++ 4.5.2.
Здравствуйте, Abyx, Вы писали:
A>Какие идиомы следует использовать, если нельзя использовать исключения?
Рекомендуется использовать RAII (как и в случае с исключениями), но таки да, придется выносить всю логику конструирования объектов из конструкторов в init'ы. Из этого следует, что нужно обязательно запрещать конструктор копирования и оператор присваивания для всех классов. Вместо bool предпочтительнее использовать коды ошибок.
Тогда типичный класс будет выглядеть вот так:
#define CHECK(X) { error_code code = (X); if (success(code)) return code; }
#define CHECK_ALLOC(X) { if (!(X)) return error_no_memory; }
потому что запаришься руками везде писать.
Так же рекомендуется тестировать приложение с всяческими тулзами вроде AppVerifier или Driver Verifier с опцией Low Resources Simulation, потому что все равно что нибудь да пропускаешь.
STL отпадает на 99.99%, остается лишь прекрасный std::auto_ptr и может еще что-то по мелочам.
Для хранения данных приходится пользоваться готовыми hash table'ами, r/b или avl tree из C и/или писать свои поделки/обертки.
U>а я фанат U>ненавижу if-ы, они увеличивают цикломатическое число и негавтивно влияют на ковередж (юнит-тестами замучаешься все decisions покрывать) U>хорошо, когда if находится в классе, а не у каждого клиента этого класса
Попробуй побыстрому свою программу, не переписывая затащить на ucLinux на какое-нибудь устройство х)
Здравствуйте, igna, Вы писали:
I>Здравствуйте, MasterZiv, Вы писали:
MZ>>Без иключений нельзя использовать STL, потому что оно их кидает.
I>Какие исключения кидает STL?
out_of_range, invalid_argument, length_error
I>(Именно STL, а не Standard Library; и именно сама STL, а не пользовательские функции, вызываемые из STL.)
Как считать, bad_alloc у аллокатора кидает пользовательская функция или нет, если аллокатор стандарный ?
On 30.07.2011 13:15, x-code wrote:
> Да, чем-то подобным постоянно пользуюсь. Не люблю исключения — с ними получается > так, что в программе возникает как-бы еще один "скрытый", неочевидный слой > выполнения.
Во-во, об этом я и говорил. Они этого "неочевидного слоя выполнения"
боятся как огня. Те, кто не понимает исключения и другие виды альтернативных
потоков управления.
Вызываешь функцию — и нет никакой гарантии, что не вывалишься > куда-нибудь в совершенно другое место. Т.е. если использовать исключения — то > нужно их ВЕЗДЕ использовать, кажый чих заворачивать в try/catch, перед > использованием каждой функции думать, а какие там она исключения может > выбросить, и т.д. Даже при отладке ставить точки останова нужно не только на > строку после функции, но и на все catch, которые могут перехватывать исключения. > Короче, goto 2.0
Ой, какое блин горе. Для сведения, ставить точку прерывания в catch бесполезно,
потому что ПОЗДНО, стек уже раскручен, к тому же почти все отладчики умеют при
выкидывании исключения ставить точки прерывания.
> А если не использовать исключения — нужно просто сделать, чтобы функции > возвращали код возврата, и все. Ну и не забывать писать проверки, но это у меня > как-то на автомате получается уже
Ну, поздравляю, значит у тебя стабильная привычка писать говнокодильник.
> А есть еще исключения из конструкторов — любимая тема на всяких собеседованиях.