Try catch в конструкторе

Обновлено: 09.05.2024

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

Использование блоков try/catch/finally для восстановления после ошибок или высвобождения ресурсов

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

Очистите ресурсы, выделенные с помощью инструкций using или блоков finally . Рекомендуется использовать инструкции using для автоматической очистки ресурсов при возникновении исключений. Используйте блоки finally , чтобы очистить ресурсы, которые не реализуют IDisposable. Код в предложении finally выполняется почти всегда — даже при возникновении исключений.

Обработка общих условий без выдачи исключений

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

Если состояние подключения перед закрытием не проверяется, исключение InvalidOperationException можно перехватить.

Выбор конкретного способа зависит от того, насколько часто ожидается возникновение данного события.

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

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

Устранение исключений при разработке классов

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

Другой способ устранения исключений заключается в том, что для наиболее общих и часто встречающихся ошибок следует возвращать значение NULL (или значение по умолчанию). Такие ошибки могут относиться к обычному потоку управления. Возвращая значение NULL (или значение по умолчанию) в таких случаях, можно уменьшить влияние на производительность приложения.

При выборе типа значения Nullable или значения по умолчанию в качестве индикатора ошибки учитывайте особенности приложения. При использовании Nullable default принимает значение null , а не Guid.Empty . В некоторых случаях добавление Nullable помогает более точно определить, присутствует или отсутствует значение. Но в определенных ситуациях добавление Nullable может привести к созданию лишних необязательных случаев для проверки, что повышает вероятность ошибки.

Выдача исключений вместо возврата кода ошибки

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

Создавайте новый класс исключений, только если предопределенное исключение не подходит. Пример:

Вызывайте исключение InvalidOperationException, если значение свойства или вызов метода не соответствуют текущему состоянию объекта.

Порождайте исключение ArgumentException или одного из предварительно определенных классов, которые являются производными от ArgumentException, если передаются недопустимые параметры.

Завершайте имена классов исключений словом Exception

Если требуется пользовательское исключение, присвойте ему соответствующее имя и сделайте его производным от класса Exception. Пример:

Включение трех конструкторов в пользовательские классы исключений

Exception(), использующий значения по умолчанию.

Обеспечение доступности данных об исключении при удаленном выполнении кода

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

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

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

Предоставление дополнительных свойств в пользовательских исключениях по мере необходимости

Размещение операторов throw для удобной трассировки стека

Трассировка стека начинается в операторе, породившем исключение, и завершается оператором catch , перехватывающим это исключение.

Использование методов построителя исключений

Обычно класс генерирует одно и то же исключение из различных мест своей реализации. Чтобы избежать повторения кода, используйте вспомогательные методы, создающие исключение и затем возвращающие его. Пример:

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

Восстановление состояния, если методы не выполняются из-за исключения

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

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

Один из способов обработки в этой ситуации заключается в перехвате всех исключений, выданных транзакцией начисления средств, и откате транзакции списания средств.

В этом примере показано использование throw для повторного порождения исходного исключения. Это позволяет вызывающим объектам проще установить фактическую причину проблемы, не обращаясь к свойству InnerException. Альтернативным способом является выдача нового исключения с включением исходного исключения в качестве внутреннего:

If a constructor throws an exception, the object's destructor is not run. If your object has already done something that needs to be undone (such as allocating some memory, opening a file, or locking a semaphore), this "stuff that needs to be undone" must be remembered by a data member inside the object.

Are these 2 statements not contradictory? The first one kind of implies that the try catch within a constructor is pretty much useless while the second says that it is needed to free resources. What am i missing here?

I didn't even know that the [function-try-block] tag existed. and now it has two questions! Maybe we should use instead a catchall tag [obscure-c++-features-that-nobody-ever-used] :P

5 Answers 5

You need to understand the different between the two to understand how the two sentences make sense. This answer here explains that.

They refer to different things.

The first one is related to the function- try block, i.e. a try block that includes a whole function, and, in the case of constructors, includes also the calls to the constructors of the base classes and of the member objects, i.e. stuff that is executed before the actual constructor body is run.

The morale is that, if a base class or a class member fails to constructs correctly, the object construction must fail with an exception, because otherwise your newly constructed object is left in an inconsistent state, with the base object/member objects half constructed. So, the purpose of such try block must be only to translate/rethrow such exception, maybe logging the incident. You can't do in any other way: if you don't explicitly throw inside your catch , an implicit throw; is added by the compiler to prevent the "half-constructed object" eventuality.

The second one refers to exceptions that are risen inside the body of your constructor; in this case, it says that you should use a "regular" try block to catch the exception, free the resources you have allocated up to now and then rethrow, because the destructor won't be called.

Notice that this behavior makes sense, since the implicit contract for the destructor, as for any other member function that is not the constructor, is that it expects to work on an object in a consistent state; but an exception thrown from the constructor means that the object hasn't been fully constructed yet, so this contract would be violated.

the object construction must fail with an exception, Not always, there's some code that remembers that it's in an invalid state (std::streams for instance)

@Mooing Duck: besides the fact that I reckon that behavior to be a useless complication in 99% of cases, we are talking about different things. The "invalid state" you're talking about is just a logic thing, it's actually a valid state for the object, since it is correctly constructed, as well as all its base classes and member objects. On the other hand, when a constructor throws the object is "not born", so it can't be used in any way, and if it is a member (or a base object) of an object being constructed the compiler forces such construction to fail.

You're right, I used a wrong word. It is a valid state, but I thought I'd point out that a class may fail to construct as expected, and not throw an exception, as long as it's always in a "valid" state.

@Mooing Duck: sure, but, as far as the language is concerned, if the constructor doesn't throw the object is correctly constructed, and thus can be used. :)

A vague translation to plain terms would be: function-try-blocks can only be used to translate exceptions and always use RAII and each resource should be managed by a single object, and they do not contradict. Oh, well, the translation is not exactly that, but the argument eventually leads to those two conclusions.

The second quote, from the C++FAQ lite states that the destructor will not be called for an object whose constructor did not complete. That in turn means that if your object is managing resources, and more so when it manages more than one, you are in deep trouble. You can catch the exception before it escapes the constructor, and then try to release the resources that you have acquired, but to do so you would need to know which resources were actually allocated.

The first quote says that a function try block within a constructor must throw (or rethrow), so the usefulness of it is very limited, and in particular the only useful thing that it can do is translate the exception. Note that the only reason for the function try block is to catch an exception during the execution of the initialization list.

But wait, is the function try block not a way to handle the first problem?

Well. not really. Consider a class that has two pointers and stores memory in them. And consider that the second call might fail, in which case we will need to release the first block. We could try to implement that in two different ways, with a function try block, or with a regular try block:

We can analyze first the simple case: a regular try block. The constructor in the first initializes the two pointers to null ( : p(), q() in the initialization list) and then tries to create the memory for both objects. In one of the two new type an exception is thrown and the catch block is entered. What new failed? We do not care, if it was the second new that failed, then that delete will actually release p . If it was the first one, because the initialization list first set both pointers to 0 and it is safe to call delete on a null pointer, the delete p is a safe operation if the first new failed.

Now on the example on the right. We have moved the allocation of the resources to the initialization list, and thus we use a function try block, which is the only way of capturing the exception. Again, one of the news fail. If the second new failed, the delete p will free the resource allocated in that pointer. But if it was the first new that failed, then p has never been initialized, and the call to delete p is undefined behavior.

Going back to my loose translation, if you used RAII and only one resource per object, we would have written the type as:

In this modified example, because we are using RAII, we do not really care about the exception. If the first new throws, no resource is acquired and nothing happens. If the second throw fails, then p will be destroyed because p has been fully constructed before the second new is attempted (there is sequence point there), and the resource will be released.

So we do not even need a try to manage the resources. which leaves us with the other usage that Sutter mentioned: translating the exception. While we must throw out of the failing constructor, we can choose what we throw. We might decide that we want to throw a custom made initialization_error regardless of what was the internal failure in the construction. That is what the function try block can be used for:

Is is not advisable to include the try/catch block within my Constructor? I know I could have the Constructor throw the Exception back to the caller. What do you guys prefer in calling methods like I have done in Constructor? In the calling class would you prefer creating an instance of FileDataValidator and calling the methods there on that instance? Just interested to hear some feedback!

More concerning is what you would do with e in the case of an API, printStackTrace smells funny. Surely you should let the users of the code encounter the exception so that they may do something about it? That's what exceptions are for.

Why not change the validateXXX operations to return booleans and then set a variable called valid to be if all three validateXXX calls are valid. Then expose that var out with a method isValid

Doing something with the Command Pattern may help here; that is, instantiate a method you'll be calling multiple times, passing in the data to validate, and let that method do the exception throwing.

3 Answers 3

In the code you show, the validation problems don't communicate back to the code that is creating this object instance. That's probably not a GOOD THING.

Variation 1:

If you catch the exception inside the method/constructor, be sure to pass something back to the caller. You could put a field isValid that gets set to true if all works. That would look like this:

Variation 2:

Or you could let the exception or some other exception propagate to the caller. I have shown it as a non-checked exception but do whatever works according to your exception handling religion:

Variation 3:

The third method I want to mention has code like this. In the calling code you have to call the constructor and then call the build() function which will either work or not.

Here is the class code:

Of course, the build() function could use a isValid() method that you call to see if its right but an exception seems the right way to me for the build function.

Variation 4:

The fourth method I want to mention is what I like best. It has code like this. In the calling code you have to call the constructor and then call the build() function which will either work or not.

This sort of follows the way JaxB and JaxRS work, which is a similar situation to what you have.

Говорят, что конструктор конструктор и деструктор класса не должны вырабатывать исключения?
В тоже время, я видел огромное количество кода и даже примеров из книг, где в конструкторе выделяется память при помощи оператора `new`, но ведь даже он может вырабатывать исключение `bad_alloc` — выходит, что весь этот код потенциально опасный? Как с этим бороться?

Исключения, ровно как и оператор return прерывают поток выполнения команд функции, из системного стека выбираются объекты (такие как локальные переменные) и для них вызываются деструкторы. Однако, если при выполнении оператора return раскрутка стека прекратиться в точке где была вызвана завершенная функция, то при при выполнении throw объекты из стека будут уничтожаться до тех пор, пока управление не будет передано в блок try<> , содержащий обработчик, соответствующий типу выброшенного исключения. Читать подробнее про обработку исключений [1].

Исключения в деструкторе класса

В связи с этим, в большинстве случаев разрушение объектов созданных на стеке (без использования new/malloc ) произойдет корректно — вызовом деструктора. Однако исключения в конструкторе или деструкторе могут приводить к нежелательным последствиям.

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

destructor_exception_abort

Тем более очевидно, что если деструктор завершает работу исключением, то может возникать утечка памяти — в памяти могут остаться как части текущего класса, так и базовых классов. Из этого ясно, что деструктор никогда не должен вырабатывать исключения, а также обрабатывать все возможные исключения функций, которые вызывает — они могут приводить как к утечкам, так и к очень трудноуловимым ошибкам.

Еще один из аспектов работы деструкторов и исключений иллюстрирует следующий фрагмент кода:

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

Стандартная библиотека предоставляет функцию std::uncaught_exception , которая в деструкторе позволяет узнать, почему уничтожается объект, из-за выброшенного исключения, или же по какой-либо другой причине.

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

Исключения в конструкторе класса

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

Стандарт языка С++ гарантирует, что если исключение возникнет в конструкторе, то памяти из под членов-данных класса будет освобождена корректно вызовом деструктора — т.е. если вы используете идиому RAII [2], то проблем не будет. Часто для этого достаточно использовать std::vector/std::string вместо старых массивов и строк, и умные указатели вместо обычных [3]. Если же вы продолжите использовать сырые указатели и динамически выделять память — нужно будет очень тщательно следить за ней, например в следующем фрагменте кода нет утечки, т.к. исключение будет выработано только если память не будет выделена [4]:

I saw many examples but I am not able to understand, how to use try catch with a simple constructor, I wrote a sample program:

  1. This program gives a compilation error
  2. If exception is caught, what happens to object ??

Well since the code has a compilation error we can't answer question 2 because the code is meaningless.

Just a hint for the future, when asking a question about compilation error, add the exact error messages to the question, and all messages.

6 Answers 6

The try/catch code is supposed to be together, you can't have one without the other. Something like this is what you're after:

See the following program for a complete working example:

With the throw 42 in there, you see:

meaning that main has caught the exception coming from the constructor. Without the throw , you see:

because everything has worked.

The main problems with your code seem to be:

You have a try statement where it shouldn't be. Try/catch blocks should generally be within a function or method, you have it immediately after the public keyword.

If you're throwing an exception from the constructor, you don't catch it in the constructor. Instead you catch it in the code that called the constructor ( main in this case).

As previously mentioned, try and catch go together, they're not standalone entities.

If you are trying to throw and catch within the constructor, you'll still need to put it within the constructor itself, something like:

which gives you:

Note specifically how the try/catch block is both complete and within the constructor function.

m trying to put try catch in class, it is illegal, as it shows compilation error : expected unqualified-id before 'try'

@user1160090 you need the try-catch block inside a function (such as the constructor). Yours is partially hanging out in the class declaration body.

@user1160090, your try is outside any code area, you need to put it inside some sort of function, not immediately after the public . It is executable code after all. In any case, if you're throwing an exception from the constructor, you don't catch it in the constructor itself, you catch it in whatever called the constructor. See my code for an example.

The issue of exceptions raised during constructors is addressed by function try blocks:

But the scenario they are addressing is a different one from your example. Function try blocks are needed when the class constructor allocates resources that need to be reclaimed. Since the destructor of the class is not run if the constructor throw (there's nothing to destroy, the class did not construct to start with) one way to address the issue is using function try blocks on the constructor. Note that constructor function try blocks must re-throw an exception or the original exception, they cannot silence the exception caught.

Читайте также: