| Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате |
| Форум программистов > C/C++: Общие вопросы > Исключение нужно бросить в конструкторе. |
| Автор: JackYF 9.1.2008, 19:13 |
| Собственно, сабж. По текущей архитектуре кода исключение должно броситься в конструкторе объекта, если инициализация прошла неуспешно. Код ошибки вернуть не могу - конструктор всё-таки А что вы делаете в таких случаях? |
| Автор: marcusmae 9.1.2008, 19:37 |
| JackYF, замечательный опрос! Я ответил, что стараюсь не бросать исключений в конструкторах, но иногда приходится. А вообще, думаю, что у меня на этот счёт пробел в теоретических познаниях : ума не приложу, что ж ещё делать, если конструктору скормили, скажем, недопустимое значение аргумента. Хуже того, это какой-нить десятый сверху базовый конструктор, который вызывается по ":" из конструктора наследуемого класса... Интересно, какие будут идеи по поводу последнего варианта ответа. |
| Автор: archimed7592 9.1.2008, 19:44 |
| Ответил 1-й вариант(надо бросить - бросаю), но есть у меня маленькое уточнение "Надо" - понятие растяжимое. Скажем так, если можно без крови перепроектировать несколько классов, чтобы бросать исключение не пришлось, то я именно так и сделаю. Другими словами - не всегда "надо", даже если кажется, что "просто необходимо" |
| Автор: nickless 9.1.2008, 19:59 |
| ИМХО если пользуешься исключениями, так пользуйся, а как раз в конструкторе удобно, не надо ничего изобретать. |
| Автор: Earnest 9.1.2008, 20:24 |
| Я выбрала первый вариант... Строго говоря, что в конструкторе исключения бросать, что в других функция - лишь бы не в деструкторе. Однако на практике я делаю все же немного по-другому, особенно если речь идет о глобально используемых классах-объектах. Такой конструктор (который может привести к исключению) я делаю приватным и заворачиваю в фабричную функцию (обычно статик этого же класса). И она возвращает уже указатель или 0, обрабатывая исключения или просто проверяя условия внутри себя. Решение зависит от контекста - ведь исключения исключениям рознь. Неправильный параметр - если только его не пользователь ввел (или он откуда-то извне появился) - это должно быть выковырено на этапе отладки - здесь лучше исключения и\или ASSERT's. А вот несуществующий файл или отсутствие ресурса или еще что-то в этом роде - другое дело. При этом, если обработка неудачного создания всегда одинакова, фабричная функция имеет преимущества - пишешь обработку один раз, а потом только if 0 return - несколько короче, чем try\catch, да еще если неоднократно... |
| Автор: archimed7592 9.1.2008, 20:44 |
Его конструктор выкинет исключение(как new T[]). |
| Автор: JackYF 9.1.2008, 20:53 |
отлично, а кто будет освобождать память из-под 4 элементов, конструкторы которых уже вызвались? |
| Автор: archimed7592 9.1.2008, 21:45 | ||
Ну, во-первых, память выделяется под всё сразу. А, во-вторых, как и в случае new T[], вызовутся деструкторы уже сконструированных элементов в порядке, обратном конструированию, потом освободится память(та что под всё сразу) и, наконец, исключение будет переброшено. |
| Автор: bsa 9.1.2008, 21:52 | ||||||
Я что-то делаю нетак? |
| Автор: JackYF 9.1.2008, 22:26 | ||
в том-то и дело, что могут не вызваться - конструктор не отработал - мы не имеем право вызывать деструктор. Добавлено через 57 секунд о, bsa, отлично, уже живой пример есть |
| Автор: archimed7592 9.1.2008, 22:28 | ||
Угу. Замени if (--counter) на if (!--counter). Добавлено через 1 минуту и 12 секунд
Так, давай, с чувством, с толком, с расстановкой - чей конструктор не отработал, чей деструктор не имеем право вызвать и т.д. |
| Автор: nickless 9.1.2008, 22:56 |
| Вот http://www.research.att.com/~bs/3rd_safe.pdf от Страуструпа [pdf, 200kB] как дополнение к его же http://www.research.att.com/~bs/bs_faq2.html#ctor-exceptionsу |
| Автор: bsa 9.1.2008, 23:41 | ||||||
Там не только это нужно было исправить.
|
| Автор: JackYF 9.1.2008, 23:47 |
итак, пример. пускай есть класс с двумя динамическими полями. Пускай в конструкторе под них выделяется память. Что будет, если я выброшу исключение после того, как память под первое поле уже выделил, а под второе ещё нет? 1) деструктор вызовется. При попытке освободить память из-под второго поля (там мусор, так как конструктор ничего не успел с ним сделать) - сегфолт. 2) деструктор не вызовется. Память, выделенная под первое поле, останется витать в воздухе. Ещё варианты? |
| Автор: archimed7592 10.1.2008, 00:08 | ||
Женя, ты сейчас жутко тупишь - завтра утром будешь долго смеятся
Отгадай к какой из двух ф-ции относится предложенный тобою конструктор Аналогично ф-циям работают и конструкторы. Если он завершится исключением, то деструктор вызван не будет, но, будут вызваны деструкторы уже сконструированных полей и деструкторы базовых классов. Почитай что-нибудь на тему базовой/строгой гарантии бессбойности в случае исключения |
| Автор: JackYF 10.1.2008, 00:32 | ||||
вообще говоря, ни к одной. Итак, мой пример:
Почему не отработал деструктор класса A? |
| Автор: archimed7592 10.1.2008, 00:40 | ||||
Если дописать rand, то очень смахивает на первую ф-цию А почему он должен отрабатывать? Деструктор вызывается для сконструированных объектов. Для недоконструированных деструктор не вызывается. Т.е. не то, что до delete pa не доходит - до самого деструктора не доходит. А вот немного модифицированный пример что выведет?
Добавлено через 3 минуты и 21 секунду Кстати, учитывая поздний час и теоритическую сонность собеседников ещё раз упомяну:
|
| Автор: bsa 10.1.2008, 00:45 | ||||
ответ на этот вопрос ты и сам знаешь. Вот archimed7592 предложил интересный вариант с использованием auto_ptr. В этом случае проблем быть уже не должно:
|
| Автор: Fazil6 10.1.2008, 01:06 |
| ну естественно к динамически создаваемым объектам это не относится. Юзаем shared_ptr и проблема решена |
| Автор: JackYF 10.1.2008, 01:06 |
Да, я задал его archimed7592у. Имелось в виду, что не всё так хорошо. Отличия (семантико-логические) конструктора от любой другой обычной фукнции в том, что конструктор может работать с динамическими объектами напрямую, так как он "знает", что всегда есть (при наличии нормального программиста Функция же должна надеяться только сама на себя - пары, которая вызовется к ней, нет, поэтому должна по возможности содержать объекты локальной области видимости, для которых в случае исключения вызовется деструктор. Только и всего. Да, неплохо. Хотя присутствует небольшой оверхед - в дополнении к вызовам двум операторов new и двум присваиваниям, мы имеем вызов двух конструкторов, двух деструкторов, двух функций. Впрочем, это должны быть мелочи... Пошёл-ка я и вправду спать... |
| Автор: archimed7592 10.1.2008, 01:22 | ||||
Выкинь эти стереотипы из головы
Конструктор, пока он не завершится тоже должен надеятся только на себя. И вообще - всё что ты описал относится к объектам, а не к конструкторам. А пока конструктор не завершился объекта никакого нет. Оверхэда нет |
| Автор: Earnest 10.1.2008, 08:47 |
| Ребята, ну вы и зацепились... Очевидно, что исключения сами по себе не панацея. И в любой другой функции (не только в конструкторе), если несколько раз выделять память и присваивать ее встроенным указателям, возникновение исключения где-нибудь посредине приведет к утечкам - если не принимать специальных мер. И не только памяти - любой ресурс - открытие файла, создание каких-нибудь объектов ядра и прочаяя. Добавим сюда идиому "Выделение ресурса есть инициализация" и все сразу щастливы. Оверхеды, конечно, есть (у "умных" указателей по сравнения с "глупыми" и прочих оберток), но при нормальном кодировании почти всегда мизерны - скажем, в случае с auto_ptr в релиз-версии будет просто присваивание (а не вызов конструктора) ну и т.д. archimed7592 молодец, для 2 часов ночи складно излагаешь |
| Автор: Lazin 10.1.2008, 09:09 |
| Очевидно, что если в ручную управлять памятью, код будет совсем немного, но быстрей, но это не стоит безопасности по отношению к исключениям, и прочих радостей ручного управления памятью. |
| Автор: JackYF 10.1.2008, 12:18 |
| Спасибо всем за за объяснения, вопрос закрыт. |
| Автор: Earnest 10.1.2008, 12:19 | ||
Это смотря какой код ты имеешь в виду. Если то, что пишешь руками, то как раз наоборот: все эти идиомы очень выразительны, так что к-во строк заметно уменьшается. А если то, что генерирует компилятор - то здесь тоже далеко не все очевидно. |
| Автор: Lazin 10.1.2008, 12:27 |
| Earnest, я имею ввиду скорость выполнения кода, так как обычно, оправдание не использования auto_ptr, shared_ptr и т.п. является как-раз якобы имеющее место падение производительности. Лично я стараюсь как можно реже использовать "голые" указатели, и чаще "умные", чего и всем желаю)) |
| Автор: UnrealMan 10.1.2008, 20:12 | ||
А я и в деструкторе не стесняюсь бросать |
| Автор: archimed7592 10.1.2008, 20:14 |
А что дальше? Abnormal termination? Или всё же "корректная" работа? |
| Автор: UnrealMan 10.1.2008, 20:18 |
| А дальше ловим его в том же деструкторе и обрабатываем функтором-обработчиком (при этом деструктор не знает, как именно должна происходить обработка исключения). |
| Автор: bsa 10.1.2008, 20:23 | ||
Тут речь идет о выкидывании исключений наружу. Если обработка осуществляется внутри функции (как конструктора, так и деструктора), то проблем вообще никаких нет. |
| Автор: JackYF 10.1.2008, 20:23 |
| UnrealMan, да, ты нереальный человек |
| Автор: archimed7592 10.1.2008, 20:25 | ||
Как же я сразу не догадался в чём подвох В принципе, для записи логов и т.п. решение очень даже красивое(тот самый throw; во внешней ф-ции, да? |
| Автор: baldina 10.1.2008, 20:27 | ||
деструктор не вызовется, т.к. до выхода из деструктора объект не считается полностью сконструированным, его еще не существует (есть только область памяти, под него отведенная), поэтому уничтожать это "нечто" мы не имеем права. Добавлено через 3 минуты и 1 секунду ступил - до конца не дочитал Добавлено через 6 минут и 22 секунды главного то не написал: надо бросить - бросаю. хотя конечно не ожидая исключений как-то комфортнее |
| Автор: UnrealMan 10.1.2008, 20:48 | ||||
Ну, да. Допустим, объект по уничтожении должен сбросить некоторую инфу в файл. Но по какой-то причине запись в файл оказалась невозможной (файл заблокирован, не хватило места на диске). Мы можем попросить пользователя закрыть кое-какие программы или освободить место на диске. Как именно обращаться к пользователю, классу уничтожаемого объекта знать не обязательно, ибо это не его дело. Достаточно, чтобы деструктор откуда-то получил функциональный объект, обрабатывающий исключения (например, он может быть сохранён в объекте при конструировании). При этом возможны две ситуации: объекту-обработчику (при помощи доброго пользователя) удалось разрешить проблему, либо не удалось. Если удалось, мы можем повторить попытку записи в файл. Если не удалось, просто выходим из деструктора (какие-то данные будут потеряны, но такова уж судьба - ничего лучше сделать не смогли). Описание handler-а и деструктора выглядит примерно так:
Кстати, конструктор тоже может следовать этой методике. Зачем сразу сдуваться, если можно вежливо попросить пользователя разрешить проблему и доконструроваться себе дальше? |
| Автор: JackYF 10.1.2008, 21:12 |
| UnrealMan, честно говоря, не понял сиих конструкций. При возникновении нештатной ситуации в деструкторе я лучше сделаю запись в логах, что всё плохо или не очень, и выйду из деструктора. |
| Автор: UnrealMan 10.1.2008, 21:19 | ||
Зачем так пессимистично поступать? Вдруг нештатную ситуацию удастся разрешить и во вполне штатном режиме выполнить функции деструктора? Добавлено @ 21:20 Шо не понятно? Выбросили исключение, поймали, вызвали обработчик, в обработчике исключение сгенерировали повторно и обработали Добавлено @ 21:23 Класс, которому принадлежит деструктор, об обработчике почти ничего не знает - т.е., например, способ обращения к пользователю не будет захардкоден в деструктор, и это есть хорошо |
| Автор: JackYF 10.1.2008, 21:43 |
Как эта штука будет масштабироваться на несколько деструкторов. Да и вообще - мой мозг на сейчас отказывается воспринимать эту структуру кода. |
| Автор: archimed7592 10.1.2008, 21:47 | ||||
Запусти:
|
| Автор: UnrealMan 10.1.2008, 22:01 |
Тут не стоит такая задача. Желательно, чтобы обработка исключений и функционал деструктора были разнесены, а не перемешивались (знакомая парадигма, не так ли?). Вот на это данный код и нацелен. |
| Автор: JackYF 10.1.2008, 22:02 |
так, уже лучше. Каково предназначение этого куска кода? |
| Автор: UnrealMan 10.1.2008, 22:27 |
Т.к. мы хотим заставить исключения обрабатываться в этих catch-блоках (внутри функции/функтора-обработчика), то исключение надо генерировать внутри соответствующего try-блока. А поскольку изначально исключение генерируется не в этом try-блоке, а в другом месте (где-то в деструкторе), то нужно поймать это исключение и повторно сгенерировать его внутри данного try-блока, а иначе внутрь этого try-блока оно никак не попадёт. Я понятно выражаюсь? |
| Автор: archimed7592 10.1.2008, 22:29 | ||
Эммм, ок, дабы немного придать смысл коду:
|
| Автор: JackYF 10.1.2008, 22:34 |
| Теперь понял, спасибо вам обоим. |
| Автор: Mayk 11.1.2008, 20:14 |
| есть мнение что в подобном случае следует заводить ф-цию init и вызывать её, дабы в случае чего деструктор уничтожал объект. вообще стараюсь исключения не кидать. а если кидать то в основном из ASSERT'ов. |
| Автор: vadiml 12.1.2008, 16:12 |
| как писали выше, можно заворачивать вызов конструктора. Правда я для этого не пишу специальную статическую функцию. Такое надо очень редко и хватает обычной функции, которая вернёт указатель или NULL, а далее if ( retval == NULL ) { что-то по этому поводу сделать }. Так же в такую функцию часто добавляю вывод в stderr, что бы сразу это видеть. |
| Автор: chipset 21.1.2008, 05:29 | ||
| Пишу специальное исключение. Если не сгенерировался обьект то лучше всего убить программу пока не поздно и записать в лог подробности. Допустим на серверах так и делается обычно. А что-бы избежать ситуации: имеем два обьекта А и Б, А инициализирован, Б нет -- чо делать ё? стараюсь что-бы у каждого обьекта был только один указатель на данные которые он обрабатывает. Это выглядит вот так:
|
| Автор: UnrealMan 25.1.2008, 15:05 | ||
Использовать smart pointer-ы или try + catch. |
| Автор: Lycifer 4.8.2008, 16:32 |
| Вообще исключения в конструкторах очень опастно пример class A { public: A() { throw 1; } }; class B : A { public: B() { } }; решение проблемы class A { protected: bool IsCreateBad; A(bool) { } public: A():IsCreateBad(false) { throw 1; } bool getCreateObject() { return IsCreateBad; } }; class B : A { protected: B(bool isCreateBad) { } public: B() try { } catch(...) { (*this) = B(true); } }; Добавлено через 11 минут и 46 секунд Ну почемуто возникает assert вопрос почему? |
| Автор: bsa 4.8.2008, 23:31 |
| Lycifer, проблемы нет там, где ты думаешь, что она есть. Так как если конструктор кидает исключение, значит он не смог сконструировать объект класса, значит конструктор потомка не должен даже запускаться. |
| Автор: SABROG 5.8.2008, 00:07 |
| Так забавно наблюдать за зверушками, которые копашатся в клетке пытаясь решить какие-то проблемы. Сразу вспоминается Си и ассемблер, где вся ответственность ложится на тебя. И в качестве бонуса дается некоторая порция свободы действий. По теме. Исключения почти никогда не использовал, т.к. небыло необходимости. Обычно проблемы решаются достаточно тривиальными проверками на уровне возвращаемого значения true или false. Уж не знаю как вы, а для меня исключения это не более чем int 3 или деление на 0, а остальное от лукавого. |
| Автор: vinter 5.8.2008, 07:22 | ||
значит ты просто не понимаешь исключений. |
| Автор: W4FhLF 5.8.2008, 08:33 | ||
Это тебя не красит. Почитай http://forum.vingrad.ru/forum/topic-216818.html |
| Автор: Lycifer 5.8.2008, 09:49 | ||
А потом оброщение к не существующей памяти, или try и catch где объект создаётся ставить?(Лучше это сделать в класе вот только не получилось |
| Автор: vinter 5.8.2008, 10:34 | ||
без try\catch будет вызван termination(); и программа упадет. |
| Автор: Partizan 5.8.2008, 10:53 |
| Кстати говоря, двухфазные конструкторы в Symbian решают аналогичные проблемы... |
| Автор: Torsten 5.8.2008, 11:27 | ||
хотя в основном, практически всегда создаю метод Init, где и произвожу все сложную инициализацию, которая может привести к проблемам. Вообще исключение не люблю, код от них бухнет и его трудно читать, поэтому стараюсь их всегда избегать. |
| Автор: Lazin 5.8.2008, 11:39 | ||
еще один пациент |
| Автор: Lycifer 5.8.2008, 12:41 | ||
|
| Автор: Peter 5.8.2008, 13:03 | ||||
И я так же поступаю. Конструкторы у меня ничего опасного не делают. Всё опасное выносится в функцию инициализации - а ей-то уж возвращать значение не запрещается. |
| Автор: UnrealMan 5.8.2008, 14:16 |
Это хорошо. А вот создавать невалидный объект и потом его инициализировать, притом не допуская иного способа инициализации объекта - это определённо нехорошо. |
| Автор: Torsten 5.8.2008, 16:14 |
а чего это я больным стал ? |
| Автор: vinter 5.8.2008, 16:24 |
паническая болезнь исключений |
| Автор: Lazin 5.8.2008, 18:23 |
будем тебя учить любить исключения |