Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

Конструктор, обработка исключений при выделении памяти

245
04 августа 2010 года
~ArchimeD~
1.4K / / 24.07.2006
Есть класс, реализующий, грубо говоря, пакет для передачи по сети. Есть желание тело пакета делать не фиксированного размера, а динамически выделять память в зависимости от типа передаваемых данных.
Код:
class Package
{
     ...
     char *body;
     ...
public:
     ...
     Package (int size);
     ~Package ();
     ...
};

Package::Package (int size)
{
     body=new char [size];
}

Package::~Package ()
{
     delete [] body;
}

В данному случае, при возникновении исключения в конструкторе, его нужно как-то обрабатывать.
Что я нагуглил и почему мне это не понравилось:
  1. Использовать отдельный метод init и обрабатывать исключения там - для пакета слишком сложно. Придется помимо создания объекта, явно его инициализировать. Есть риск непроинициализировать объект. Поэтому еще в каждом методе придется лепить проверку, инициализирован ли объект. Громоздко и некрасиво.
  2. Использовать умные указатели - да, они по идее, если что, подчистят память. Но вопрос проверки существования объекта остается открытым.
  3. Использовать фабрику объектов (вот тут вот предлагают) - имхо сложновато и сродни забиванию гвоздей микроскопом.
  4. Насколько я понял, генерировать в конструкторе исключение, а затем ловить его за пределами класса - сами же и пишут, что не айс, и иногда странно работает.
В архиве видел еще тему - http://forum.codenet.ru/archive/index.php/t-32078.html, но она старая и кажись ничего нового мне не сказала.
Интересует мнение, как выделять память (да и вообще проводить потенциально опасные на исключения операции) в объектах красиво и правильно.
5
04 августа 2010 года
hardcase
4.5K / / 09.08.2005
Однозначно использовать автоуказатели.
Если произойдет исключение в конструкторе, то автоуказатели за собой уберут, а вот исключение пойдет дальше и объект так и не изготовится.
245
04 августа 2010 года
~ArchimeD~
1.4K / / 24.07.2006
Правильно. Но с самим то объектом несозданным что делать? Вернее как отдетектить, что в конструкторе бяка приключилась?
5
04 августа 2010 года
hardcase
4.5K / / 09.08.2005
Цитата: ~ArchimeD~
Правильно. Но с самим то объектом несозданным что делать? Вернее как отдетектить, что в конструкторе бяка приключилась?

Исключение же вывалится :)

87
04 августа 2010 года
Kogrom
2.7K / / 02.02.2008
Надо немного конкретики добавить. Что мешает использовать std::string, std::vector и т.д.? Пусть body само за себя отвечает.
245
04 августа 2010 года
~ArchimeD~
1.4K / / 24.07.2006
2hc
ок, так?
 
Код:
try
{
     Package package (1024);
}
catch (bad_alloc xa)
{
     ...
}

Мне такой подход как-то кажется кривоватым - это раз. А во-вторых, если он и прокатит, принципы инкапсуляции тихо плачут в уголке. Вопрос в том, что конструктор не возвращает значений, а за пределами конструктора нужно бы знать, можно ли использовать объект.
245
04 августа 2010 года
~ArchimeD~
1.4K / / 24.07.2006
Цитата: Kogrom
Надо немного конкретики добавить. Что мешает использовать std::string, std::vector и т.д.? Пусть body само за себя отвечает.



Вопрос скорее абстрактный про исключения, и не только про выделение памяти для динамических массивов. Но могу сказать для данного конкретного случая, чем не нравятся стандартные библиотеки - данный буфер предназначен только для того, чтобы его многократно заполнять и кидать в сокет. У шаблонов совершается много телодвижений, несущих дополнительную нагрузку на процессор - это и конструктор, это и проверки индексов и т.д. и т.п. (по крайней мере такое у меня представление о мире)

87
04 августа 2010 года
Kogrom
2.7K / / 02.02.2008
Цитата: ~ArchimeD~
Вопрос скорее абстрактный про исключения, и не только про выделение памяти для динамических массивов.


А какие там ещё исключения могут быть, кроме исключения, связанного с невозможностью выделить память? Деление на ноль?

Ну, например, нет возможности выделить память (в конкретном случае - килобайт). Почему нет возможности? Что делать? Завершать программу? Переходить на резервный режим работы? Уничтожать соседние процессы, чтобы они отдали память? Универсального ответа нет. Но вроде бы такая проблема не должна скрываться в отдельном маленьком классе.

Цитата: ~ArchimeD~
У шаблонов совершается много телодвижений, несущих дополнительную нагрузку на процессор - это и конструктор, это и проверки индексов и т.д. и т.п. (по крайней мере такое у меня представление о мире)


Green-а на тебя нет... Да и проверки индексов там нет, точнее, она идёт как опция, которую можно использовать, если есть большая потребность. В C++ придерживались мнения, что скорость важнее безопасности, потому издержки там копеечные.

Всё равно, в конечном итоге наворотишь свой аналог-велосипед.

Другое дело, что исключения могут появляться и при работе со стандартными контейнерами. Но они дают какие-то гарантии своего поведения в случае сбоев. Например, они гарантируют, что в деструкторе не будет исключений.

245
04 августа 2010 года
~ArchimeD~
1.4K / / 24.07.2006
Kogrom, предлагаю издержки ресурсов не обсуждать. Откуда ты знаешь что кому надо - может программы для маломощных embedded, а может высоконагруженный сервер с критичным временем отклика.
Вопрос прозвучал: как по хорошему детектить возникновение исключений в конструкторе за пределами объекта, чтобы знать, можно ли использовать объект. Дополню - если это невозможно, какой вариант предпочтительней? Двухэтапная инициализация? Или скажем внутри конструктора ловить ексепшн и многократно пытаться выделить память?
Еще поясню - не суть что делать дальше с объектом, главное не использовать его, если случился эксепшн.
Я кстати вектор не исключаю категорически, но пока мне такой вариант не кажется самым предпочтительным.
5
04 августа 2010 года
hardcase
4.5K / / 09.08.2005
Цитата: ~ArchimeD~

Вопрос прозвучал: как по хорошему детектить возникновение исключений в конструкторе за пределами объекта, чтобы знать, можно ли использовать объект.

Никак. Если произошло исключение во время создания объекта, то память выделенная под него освобождается (деструкторы его предков кстати вызываются) и он считается не созданным вовсе - использовать его нельзя.
Что делать дальше - решает программист. Есть кстати подход - в начале работы программы выделяется вся необходимая память и только с ней происходит работа впоследствии.

245
04 августа 2010 года
~ArchimeD~
1.4K / / 24.07.2006
Цитата: hardcase
Никак. Если произошло исключение во время создания объекта, то память выделенная под него освобождается (деструкторы его предков кстати вызываются) и он считается не созданным вовсе - использовать его нельзя.


Вот. Значит что остается - фабрика, двухэтапная инициализация, стандартные шаблоны. Еще есть варианты?

5
04 августа 2010 года
hardcase
4.5K / / 09.08.2005
Цитата: ~ArchimeD~
Вот. Значит что остается - фабрика, двухэтапная инициализация, стандартные шаблоны. Еще есть варианты?


И чего ради вся эта свистопляска?

287
04 августа 2010 года
Shiizoo
958 / / 14.03.2004
Это я так после perl'а не мог понять, почему в C++/Java конструктором нельзя вернуть undef какой-нить. xD

Разобрано на парашифт дот ком. Не понимаю, а чем исключение плохо?
87
04 августа 2010 года
Kogrom
2.7K / / 02.02.2008
Цитата: ~ArchimeD~
2hc
ок, так?
 
Код:
try
{
     Package package (1024);
}
catch (bad_alloc xa)
{
     ...
}

Мне такой подход как-то кажется кривоватым - это раз. А во-вторых, если он и прокатит, принципы инкапсуляции тихо плачут в уголке.



Почему кривоватый? Всё правильно. Ну можно ловить исключение не на создании единственного объекта, а на функцию, даже на основную подпрограмму. Если исключение сработает, то переходим на резервную. Всё зависит от конкретики.

Возможно, и в самом конструкторе Package в цикле пытаться выделить память, пока не получится, производя очищающие меры при срабатывании исключения (или хотя бы задержки). Конкретный поток зависнет на какое-то время, но, возможно, в конкретном случае нас это устраивает.

Вот тебе и управление от максимума до минимума.

87
04 августа 2010 года
Kogrom
2.7K / / 02.02.2008
Читал Страуструпа и Саттера по этой теме. Не осилил до полной ясности. Одно понял: ловите исключение там, где у вас достаточно данных для того, чтобы его правильно обработать.

В общем случае, в самом Package такой информации нет. Поэтому финты внутри него не имеют смысла. Разве что, можно попытаться поймать стандартное исключение и сгенерировать своё самодельное, с дополнительными данными. Но чую, что это зло.
245
05 августа 2010 года
~ArchimeD~
1.4K / / 24.07.2006
Цитата: Kogrom
Читал Страуструпа и Саттера по этой теме. Не осилил до полной ясности. Одно понял: ловите исключение там, где у вас достаточно данных для того, чтобы его правильно обработать.

В общем случае, в самом Package такой информации нет. Поэтому финты внутри него не имеют смысла. Разве что, можно попытаться поймать стандартное исключение и сгенерировать своё самодельное, с дополнительными данными. Но чую, что это зло.



По идее, создавая объект, я знать не знаю, какое там исключение может внутри возникнуть. В этом и загвоздка. В голову еще бред пришел, по типу двойной инициаизации

Код:
#include <iostream>
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;
}

И кстати, вполне возможно, что, внутри стандартных контейнеров исключения не ловятся.
Ладно, я думаю, тут уже играют свою роль недостатки языка.
245
05 августа 2010 года
~ArchimeD~
1.4K / / 24.07.2006
Есть еще не очень здоровая идея сделать конструктор типа
 
Код:
test::test (bool &result)
{
     ...
     catch (...)
     {
           result=false;
     }
}


ЗЫ: пошел читать герба саттера, авось разберусь
287
05 августа 2010 года
Shiizoo
958 / / 14.03.2004
Странное какое-то разыменовывание ссылки на булев.
245
05 августа 2010 года
~ArchimeD~
1.4K / / 24.07.2006
Цитата: Shiizoo
Странное какое-то разыменовывание ссылки на булев.


поправил

535
05 августа 2010 года
Нездешний
537 / / 17.01.2008
А вот некий товарищ Касперски вообще имеет мнение, что проверять успешность выделения памяти следует далеко не всегда ;)
http://www.insidepro.com/kk/197r.shtml

ЗЫ А вообще вариант с двойной инициализацией смотрится не так уж и плохо, если это действительно нужно
245
05 августа 2010 года
~ArchimeD~
1.4K / / 24.07.2006
Цитата: Нездешний
А вот некий товарищ Касперски вообще имеет мнение, что проверять успешность выделения памяти следует далеко не всегда ;)



У меня задумка примерно такая - в многопоточной программе, в случае неуспешного выделения памяти, вызвавший new поток должен попытаться порвать соединение, высвободить зажранные им самим ресурсы и засуспендится. или даже завершиться вообще (что лучше). Остальные потоки должны попытаться отработать до конца. Если никак не обрабатывать исключение, программа, насколько я понимаю, упадет целиком. В данном случае вопрос стоит, как бы поток, не лазия особо в кишки класса Package, наиболее красиво и правильно ;) узнал про аварийную ситуацию внутри.

87
05 августа 2010 года
Kogrom
2.7K / / 02.02.2008
Цитата: ~ArchimeD~
В голову еще бред пришел, по типу двойной инициаизации


Тут надо ответить на вопрос: чем отличается установка флага ошибки от исключения? А тем что ты можешь запросто забыть обработать флаг ошибки. Исключение же прёт от самой внутренней функции к самой наружней и не заметить его не получится. И сделано так специально.

Зачем же глушить исключение в самом объекте? Лови его в потоке, а не в объекте. Как словит, так пусть и завершается. Ну, может в лог что-то кинет.

Цитата: ~ArchimeD~
И кстати, вполне возможно, что, внутри стандартных контейнеров исключения не ловятся.



Для контейнеров даются некоторые гарантии. Например гарантия того, что в деструкторе не будет вызвано исключение. Если же оно будет там вызвано, то программа может молчаливо завершить свою работу. И это нехорошо.

535
05 августа 2010 года
Нездешний
537 / / 17.01.2008
Тогда, может быть, в конструкторе использовать форму new, которая не дает исключений, а просто возвращает NULL. А исключение бросать вручную из методов при попытках обращения к невалидному указателю. Ну, или возвращать код ошибки
Код:
#include <new>

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;
    }
};
87
05 августа 2010 года
Kogrom
2.7K / / 02.02.2008
Цитата: Нездешний
Тогда, может быть, в конструкторе использовать форму new, которая не дает исключений, а просто возвращает NULL.



Это уместно, если вся логика содержится в этом test. Иначе мы просто упустим из внимания, что память исчерпана и оно выстрелит в другом месте. И генерить самодельные исключения не стоит. Какой смысл? Лучше уж стандартное.

1
05 августа 2010 года
kot_
7.3K / / 20.01.2000
Цитата: Нездешний
Тогда, может быть, в конструкторе использовать форму new, которая не дает исключений, а просто возвращает NULL. А исключение бросать вручную из методов при попытках обращения к невалидному указателю. Ну, или возвращать код ошибки[/CODE]


У каждой ошибки есть Фамилия Имя И Отчество.
у каждой ошибки есть эти параметры. Начинать надо с этого.

5
05 августа 2010 года
hardcase
4.5K / / 09.08.2005
Цитата: Нездешний
возвращать код ошибки

Код ошибки - трудно придумать более неприятную штуку для конечного пользователя.
Более того, указанный способ ведет к сокрытию проблемы, что в двойне тягостно для отладки.

535
05 августа 2010 года
Нездешний
537 / / 17.01.2008
[QUOTE=Kogrom]генерить самодельные исключения не стоит. Какой смысл? Лучше уж стандартное[/QUOTE][QUOTE=kot_]У каждой ошибки есть Фамилия Имя И Отчество[/QUOTE]Можно и стандартное выбросить. Суть не в этом.
Смысл ухищрения - убрать выброс исключения из конструктора, потому что это ТС не нравится:
[QUOTE=~ArchimeD~]Насколько я понял, генерировать в конструкторе исключение, а затем ловить его за пределами класса - сами же и пишут, что не айс, и иногда странно работает.[/QUOTE]и выбрасывать исключение не при выделении памяти, а при попытке ее использования
5
05 августа 2010 года
hardcase
4.5K / / 09.08.2005
Цитата: Нездешний
Можно и стандартное выбросить. Суть не в этом.
Смысл ухищрения - убрать выброс исключения из конструктора, потому что это ТС не нравится:
и выбрасывать исключение не при выделении памяти, а при попытке ее использования


Тем самым мы закладываем бомбу с часовым механизмом. Программа будет потенциально падать во всех местах где используется такой объект. Так что такое решение нельзя рассматривать как уместное.

Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог