LINUX.ORG.RU

C++ without exceptions


0

0

Предположим есть С++ компилятор который не поддерживает исключения или они принудительно отключены. Как в таком случае контролировать создание объектов, если например при инициализации одного из них возникла ошибочная ситуация в конструкторе и обьект оказался не полностью инициализированным? Создать специальный метод который возращает код успешного создания объекта и проверять его каждый раз после вызова конструктора?

anonymous

а как живут без исключений в си?

вообще надо просто реализовать некоторое подобие Maybe и жизнь будет прекрасна

anonymous
()

> Создать специальный метод который возращает код успешного создания объекта

Да. Так иногда даже с исключениями делают. В принципе можно operator bool перегрузить, если пользоваться этим везде.

Legioner ★★★★★
()
Ответ на: комментарий от grob

> Обычными сишными недо-continuations'ами: setjmp/longjmp, makecontext/setcontext/...

Вы думайте, о чем пишете! В C++ этими вещапи пользоватся запрещено, за это могу и с работы уволить.

Бонально вспомните о деструкторах, которые никогда не будут вызваны, если выход из контекста будет осуществлятся через longjump, - это сразу порождает проблему утечки памяти/ресурсов, в случае сложных обьектов, плюс фиг его знает, как плюсовый runtime еще поведет себя.

quarck
()

from http://www.parashift.com/c++-faq-lite/exceptions.html

[17.2] How can I handle a constructor that fails?

Throw an exception.

Constructors don't have a return type, so it's not possible to use return codes. The best way to signal constructor failure is therefore to throw an exception. If you don't have the option of using exceptions, the "least bad" work-around is to put the object into a "zombie" state by setting an internal status bit so the object acts sort of like it's dead even though it is technically still alive.

The idea of a "zombie" object has a lot of down-side. You need to add a query ("inspector") member function to check this "zombie" bit so users of your class can find out if their object is truly alive, or if it's a zombie (i.e., a "living dead" object), and just about every place you construct one of your objects (including within a larger object or an array of objects) you need to check that status flag via an if statement. You'll also want to add an if to your other member functions: if the object is a zombie, do a no-op or perhaps something more obnoxious.

In practice the "zombie" thing gets pretty ugly. Certainly you should prefer exceptions over zombie objects, but if you do not have the option of using exceptions, zombie objects might be the "least bad" alternative.

anonymous
()

Посмотри как в Symbian сделано. Там тоже С++ с самодельными exeption'ами. Там функции делятся на те которые могут вызвать эксепшены и те которые не могут. Используется двухфазный конструктор, сначала часть которая не вызывает эксепшенов, а потом другая которая может вызывать. Плюс свой CleanupStack для организации stack unwinding.

imp ★★
()

Очень просто. В конструкторе выполняешь только тривиальные действия, которые не могут обломаться. И делаешь специальный метод Init, который выполняет более сложные действия и возвращает код ошибки в случае неудачи.

Alviss
()

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

gzh
()
Ответ на: комментарий от gzh

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

Как вариант, можно все конструкторы класса объявить приватными и написать make-функцию (фабрику), которая будет конструировать объекты и возвращать указатели на них или NULL в случае ошибки. Можно указать её дружественной для класса или сделать статическим членом класса - это как покажется удобнее. Получится этакий "внешний конструктор".

Deleted
()
Ответ на: комментарий от Deleted

Но если тебе нужны экземпляры такого класса на стеке, то этот вариант естественно не подходит.

Deleted
()
Ответ на: комментарий от kto_tama

> с исключениями в датском королевстве все плохо

Отсутствие поддержки exception-ов не есть повод использовать setjmp/longjmp, это даже на чистом C в большинстве случаев является признаком непроффесионализма и велосипедизма. Есть несколько достаточно приемлемых способов выкрутиться в ситуациях, когда exception-ами пользовать нельзя, как минимум зомби-обьекты - раз, и вынос основного тела конструктора в bool Init(); - два.

Как альтернатива, если обьекты все предполагается аллокировать динамически, можно сделать так: Обьявить все используемые конструкторы закрытыми, конструктор копирования с оператором копирования -- уже в засимости от ситуации, возможно в общем случае просто private-ами тоже, а пользователю предоставить

static CMyClass *Create(args...);

которая будет создавать и инициализировать обьект, а в случае неудачи - возвращать NULL. Конструктор при этом опять остается тривиальным, вся инициализация должна быть внутри этого метода.

quarck
()

>если например при инициализации одного из них возникла ошибочная ситуация в конструкторе и обьект оказался не полностью инициализированным?


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

по-хорошему такие финты надо выделять в отдельные методы, возвращиющие bool.

а на вариант с нулевым указателем от malloc в конструкторе надо реагировать однозначно: assert

xydo ★★
()
Ответ на: комментарий от xydo

> а на вариант с нулевым указателем от malloc в конструкторе надо реагировать однозначно: assert

Отличная идея, если речь идет о ядре! Не удалось аллокировать непрерывный кусок памяти под прикладные нужды -- выкинем kernel panic, пусть памяти докупают!

quarck
()
Ответ на: комментарий от quarck

> Вы думайте, о чем пишете! В C++ этими вещапи пользоватся запрещено, за это могу и с работы уволить.

я отвечал на

> а как живут без исключений в си?

grob ★★★★★
()
Ответ на: комментарий от mv

CLOS конечно хорошо, но мне за него не платят.

anonymous
()
Ответ на: комментарий от xydo

> а вот нефиг писать такие конструкторы. нельзя писать такой код, чтобы объект мог быть полуинициализированным.

А вы про RAII не слышали?

anonymous
()
Ответ на: комментарий от xydo

> а на вариант с нулевым указателем от malloc в конструкторе надо реагировать однозначно: assert

Ога, а если собрать еще с NDEBUG, то будет вообще феерично.

anonymous
()
Ответ на: комментарий от grob

> я отвечал на

>> а как живут без исключений в си?

В любом случае, в C заменой exception-ам является не setjmp/longjmp, а возврат кода ошибки + проверка кодов возврата абсолютно всех вызовов без исключения, и ручная прокрутка действий в обратном направлении, в случае возникновения ошибки "где-то посредине". setjmp/longjmp даже на C использовать не стоит, я на вскидку не могу привести ни одной ситуации, где это использование может быть оправданно, ну разве за исключением "самодельных" тредов реализованных полностью в userspace-е.

quarck
()
Ответ на: комментарий от quarck

>setjmp/longjmp даже на C использовать не стоит

А как например вернутся из глубокой рекурсии чтобы сразу, учитывая что например все на стеке алокируется?

imp ★★
()
Ответ на: комментарий от imp

> А как например вернутся из глубокой рекурсии чтобы сразу, учитывая что например все на стеке алокируется?

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

Касательно использования longjmp/setjmp для выхода их рекурсии: Например вы уверены, что везде, на всех уровнях, аллокируются только стековые обьекты? Пусть даже сейчас вы в этом уверены на 100%, для того, что-бы поддерживать код в корректном состоянии, будет необходимо помнить об этом всем разработчикам, модифицирующим любую из функций, входящих в множество взаимно-рекурсивных функций, - тут вероятность ошибится уже возрастает, в один день найдется кто-нибудь, кто из такой функции откроет файл или выделит кусок памяти, надеясь закрыть его/освободить память перед выходом из блока, явным вызовом close или free, и все, здравтсвуй дебагер и тулзы для отлавливания утечек памяти.

quarck
()
Ответ на: комментарий от quarck

Кстати, longjmp/setjmp это не единственный способ быстро выйти из рекурсии: если сводить рекурсию к хвостовой, и использовать компилятор, умеющий ее оптимизировать, то любой return будет делатся даже быстрее, чем longjmp-ом.

quarck
()
Ответ на: комментарий от quarck

>Кстати, longjmp/setjmp это не единственный способ быстро выйти из рекурсии: если сводить рекурсию к хвостовой

А вам известен способ _любую_ рекурсию свести к хвостовой? Не поделитесь?

imp ★★
()
Ответ на: комментарий от imp

> А вам известен способ _любую_ рекурсию свести к хвостовой? Не поделитесь?

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

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

quarck
()
Ответ на: комментарий от quarck

Хотя, надо сказать, что если на C программу удалось записать в форме хвостовой рекурсии, значит пора выкидывать рекурсию: хвостовая рекурсия - это же по сути дела, грубо говоря, while цикл для языков, которые не содержат в себе императивных возможностей, так что можно переписать код в императивной форме.

quarck
()
Ответ на: комментарий от mv

интересно, а как реализовать на С/С++ лисповые conditions?

anonymous
()
Ответ на: комментарий от quarck

>Хотя, надо сказать, что если на C программу удалось записать в форме хвостовой рекурсии, значит пора выкидывать рекурсию: хвостовая рекурсия - это же по сути дела, грубо говоря, while цикл для языков, которые не содержат в себе императивных возможностей, так что можно переписать код в императивной форме.

Ну давайте перепишите мне парсер арифметических выражений со скобками и приоритетами операций в императивной форме ...

imp ★★
()
Ответ на: комментарий от imp

> перепишите мне парсер арифметических выражений со скобками и приоритетами операций в императивной форме ...

В императивной или без рекурсии? Или хаскеляторы уже относят рекурсию к не-императивному программированию?

tailgunner ★★★★★
()
Ответ на: комментарий от imp

> Ну давайте перепишите мне парсер арифметических выражений со скобками и приоритетами операций в императивной форме ...

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

quarck
()
Ответ на: комментарий от quarck

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

imp ★★
()
Ответ на: комментарий от imp

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

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

quarck
()
Ответ на: комментарий от quarck

Если задача тривиально решается в рекурсивной форме и извращённо решается в императивной форме, а также не требует предельной производительности, тратить время на написание и отладку сложного кода глупо.

Legioner ★★★★★
()
Ответ на: комментарий от Legioner

> Если задача тривиально решается в рекурсивной форме и извращённо решается в императивной форме, а также не требует предельной производительности, тратить время на написание и отладку сложного кода глупо.

Ну, я не высказывался в совсем категоричной форме, все зависит от требований, если вы точно уверены, что не будет более 3х-10ти вложенных вызовов, это одно, а если вы заранее не можете на подобные вещи полагатся, это уже совершенно другое.

Все зависит так-же и от требований к коду, которые могут быть совершенно различными: время разработки, потребляемые ресурсы, стабильность/точность, итп.

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

quarck
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.