Конструктор, обработка исключений при выделении памяти
{
...
char *body;
...
public:
...
Package (int size);
~Package ();
...
};
Package::Package (int size)
{
body=new char [size];
}
Package::~Package ()
{
delete [] body;
}
В данному случае, при возникновении исключения в конструкторе, его нужно как-то обрабатывать.
Что я нагуглил и почему мне это не понравилось:
- Использовать отдельный метод init и обрабатывать исключения там - для пакета слишком сложно. Придется помимо создания объекта, явно его инициализировать. Есть риск непроинициализировать объект. Поэтому еще в каждом методе придется лепить проверку, инициализирован ли объект. Громоздко и некрасиво.
- Использовать умные указатели - да, они по идее, если что, подчистят память. Но вопрос проверки существования объекта остается открытым.
- Использовать фабрику объектов (вот тут вот предлагают) - имхо сложновато и сродни забиванию гвоздей микроскопом.
- Насколько я понял, генерировать в конструкторе исключение, а затем ловить его за пределами класса - сами же и пишут, что не айс, и иногда странно работает.
Интересует мнение, как выделять память (да и вообще проводить потенциально опасные на исключения операции) в объектах красиво и правильно.
Если произойдет исключение в конструкторе, то автоуказатели за собой уберут, а вот исключение пойдет дальше и объект так и не изготовится.
Исключение же вывалится :)
ок, так?
{
Package package (1024);
}
catch (bad_alloc xa)
{
...
}
Мне такой подход как-то кажется кривоватым - это раз. А во-вторых, если он и прокатит, принципы инкапсуляции тихо плачут в уголке. Вопрос в том, что конструктор не возвращает значений, а за пределами конструктора нужно бы знать, можно ли использовать объект.
Вопрос скорее абстрактный про исключения, и не только про выделение памяти для динамических массивов. Но могу сказать для данного конкретного случая, чем не нравятся стандартные библиотеки - данный буфер предназначен только для того, чтобы его многократно заполнять и кидать в сокет. У шаблонов совершается много телодвижений, несущих дополнительную нагрузку на процессор - это и конструктор, это и проверки индексов и т.д. и т.п. (по крайней мере такое у меня представление о мире)
А какие там ещё исключения могут быть, кроме исключения, связанного с невозможностью выделить память? Деление на ноль?
Ну, например, нет возможности выделить память (в конкретном случае - килобайт). Почему нет возможности? Что делать? Завершать программу? Переходить на резервный режим работы? Уничтожать соседние процессы, чтобы они отдали память? Универсального ответа нет. Но вроде бы такая проблема не должна скрываться в отдельном маленьком классе.
Green-а на тебя нет... Да и проверки индексов там нет, точнее, она идёт как опция, которую можно использовать, если есть большая потребность. В C++ придерживались мнения, что скорость важнее безопасности, потому издержки там копеечные.
Всё равно, в конечном итоге наворотишь свой аналог-велосипед.
Другое дело, что исключения могут появляться и при работе со стандартными контейнерами. Но они дают какие-то гарантии своего поведения в случае сбоев. Например, они гарантируют, что в деструкторе не будет исключений.
Вопрос прозвучал: как по хорошему детектить возникновение исключений в конструкторе за пределами объекта, чтобы знать, можно ли использовать объект. Дополню - если это невозможно, какой вариант предпочтительней? Двухэтапная инициализация? Или скажем внутри конструктора ловить ексепшн и многократно пытаться выделить память?
Еще поясню - не суть что делать дальше с объектом, главное не использовать его, если случился эксепшн.
Я кстати вектор не исключаю категорически, но пока мне такой вариант не кажется самым предпочтительным.
Вопрос прозвучал: как по хорошему детектить возникновение исключений в конструкторе за пределами объекта, чтобы знать, можно ли использовать объект.
Никак. Если произошло исключение во время создания объекта, то память выделенная под него освобождается (деструкторы его предков кстати вызываются) и он считается не созданным вовсе - использовать его нельзя.
Что делать дальше - решает программист. Есть кстати подход - в начале работы программы выделяется вся необходимая память и только с ней происходит работа впоследствии.
Вот. Значит что остается - фабрика, двухэтапная инициализация, стандартные шаблоны. Еще есть варианты?
И чего ради вся эта свистопляска?
Разобрано на парашифт дот ком. Не понимаю, а чем исключение плохо?
ок, так?
{
Package package (1024);
}
catch (bad_alloc xa)
{
...
}
Мне такой подход как-то кажется кривоватым - это раз. А во-вторых, если он и прокатит, принципы инкапсуляции тихо плачут в уголке.
Почему кривоватый? Всё правильно. Ну можно ловить исключение не на создании единственного объекта, а на функцию, даже на основную подпрограмму. Если исключение сработает, то переходим на резервную. Всё зависит от конкретики.
Возможно, и в самом конструкторе Package в цикле пытаться выделить память, пока не получится, производя очищающие меры при срабатывании исключения (или хотя бы задержки). Конкретный поток зависнет на какое-то время, но, возможно, в конкретном случае нас это устраивает.
Вот тебе и управление от максимума до минимума.
В общем случае, в самом Package такой информации нет. Поэтому финты внутри него не имеют смысла. Разве что, можно попытаться поймать стандартное исключение и сгенерировать своё самодельное, с дополнительными данными. Но чую, что это зло.
В общем случае, в самом Package такой информации нет. Поэтому финты внутри него не имеют смысла. Разве что, можно попытаться поймать стандартное исключение и сгенерировать своё самодельное, с дополнительными данными. Но чую, что это зло.
По идее, создавая объект, я знать не знаю, какое там исключение может внутри возникнуть. В этом и загвоздка. В голову еще бред пришел, по типу двойной инициаизации
using namespace std;
class test
{
char *body;
bool ok;
public:
test (void);
bool is_ok () {return ok;};
};
test::test (void)
{
ok=true;
try
{
body=new char[-10];
}
catch (bad_alloc xa)
{
ok=false;
}
}
int main (void)
{
test obj;
if (obj.is_ok())
cout << "OK" << endl;
else
cerr << "FAIL" << endl;
return 0;
}
И кстати, вполне возможно, что, внутри стандартных контейнеров исключения не ловятся.
Ладно, я думаю, тут уже играют свою роль недостатки языка.
{
...
catch (...)
{
result=false;
}
}
ЗЫ: пошел читать герба саттера, авось разберусь
поправил
http://www.insidepro.com/kk/197r.shtml
ЗЫ А вообще вариант с двойной инициализацией смотрится не так уж и плохо, если это действительно нужно
У меня задумка примерно такая - в многопоточной программе, в случае неуспешного выделения памяти, вызвавший new поток должен попытаться порвать соединение, высвободить зажранные им самим ресурсы и засуспендится. или даже завершиться вообще (что лучше). Остальные потоки должны попытаться отработать до конца. Если никак не обрабатывать исключение, программа, насколько я понимаю, упадет целиком. В данном случае вопрос стоит, как бы поток, не лазия особо в кишки класса Package, наиболее красиво и правильно ;) узнал про аварийную ситуацию внутри.
Тут надо ответить на вопрос: чем отличается установка флага ошибки от исключения? А тем что ты можешь запросто забыть обработать флаг ошибки. Исключение же прёт от самой внутренней функции к самой наружней и не заметить его не получится. И сделано так специально.
Зачем же глушить исключение в самом объекте? Лови его в потоке, а не в объекте. Как словит, так пусть и завершается. Ну, может в лог что-то кинет.
Для контейнеров даются некоторые гарантии. Например гарантия того, что в деструкторе не будет вызвано исключение. Если же оно будет там вызвано, то программа может молчаливо завершить свою работу. И это нехорошо.
class test
{
char *body;
public:
test()
{
body = new (std::nothrow) char[COUNT];
}
void some_method()
{
if (!body) throw some_exception;
...
}
test_error some_method2()
{
if (!body) return error_bad_alloc;
...
return error_ok;
}
};
Это уместно, если вся логика содержится в этом test. Иначе мы просто упустим из внимания, что память исчерпана и оно выстрелит в другом месте. И генерить самодельные исключения не стоит. Какой смысл? Лучше уж стандартное.
У каждой ошибки есть Фамилия Имя И Отчество.
у каждой ошибки есть эти параметры. Начинать надо с этого.
Код ошибки - трудно придумать более неприятную штуку для конечного пользователя.
Более того, указанный способ ведет к сокрытию проблемы, что в двойне тягостно для отладки.
Смысл ухищрения - убрать выброс исключения из конструктора, потому что это ТС не нравится:
[QUOTE=~ArchimeD~]Насколько я понял, генерировать в конструкторе исключение, а затем ловить его за пределами класса - сами же и пишут, что не айс, и иногда странно работает.[/QUOTE]и выбрасывать исключение не при выделении памяти, а при попытке ее использования
Смысл ухищрения - убрать выброс исключения из конструктора, потому что это ТС не нравится:
и выбрасывать исключение не при выделении памяти, а при попытке ее использования
Тем самым мы закладываем бомбу с часовым механизмом. Программа будет потенциально падать во всех местах где используется такой объект. Так что такое решение нельзя рассматривать как уместное.