A расплывчато перевод в простые термины будет: function-try-blocks может использоваться только для перевода исключений и всегда используют RAII, и каждый ресурс должен управляться один объект , и они не противоречат. О, ну, перевод не совсем такой, но спор в конечном итоге приводит к этим двум выводам.
Во второй цитате из C ++ FAQ lite говорится, что деструктор не будет вызываться для объекта, конструктор которого не завершен. Это, в свою очередь, означает, что, если ваш объект управляет ресурсами, а тем более, когда он управляет более чем одним, у вас большие проблемы. Вы можете перехватить исключение до того, как оно выйдет из конструктора, а затем попытаться высвободить полученные ресурсы, но для этого вам необходимо знать, какие ресурсы фактически были выделены.
Первая цитата говорит, что блок try функции внутри конструктора должен выдавать (или rethrow ), поэтому его полезность очень ограничена, и, в частности, единственная полезная вещь, которую он может сделать, это перевести исключение. Обратите внимание, что единственная причина для блока функции try - перехват исключительной ситуации во время выполнения списка инициализации .
.
Но подождите, не является ли блок try функции способом решения первой проблемы?
Ну ... не совсем. Рассмотрим класс, который имеет два указателя и хранит память в них. И учтите, что второй вызов может завершиться неудачей, и в этом случае нам потребуется освободить первый блок. Мы можем попытаться реализовать это двумя различными способами: с помощью блока try функции или с помощью обычного блока try:
// regular try block // function try block
struct test {
type * p;
type * q;
test() : p(), q() { test() try : p( new int ), q( new int ) {
try {
p = new type;
q = new type;
} catch (...) { } catch (...) {
delete p; delete p;
throw;
} } // exception is rethrown here
}
~test() { delete p; delete q; }
};
Сначала мы можем проанализировать простой случай: обычный блок try. Конструктор в first инициализирует два указателя в null (: p(), q()
в списке инициализации), а затем пытается создать память для обоих объектов. В одном из двух new type
выдается исключение и вводится блок catch. Что new
не удалось? Нам все равно, если это был второй новый сбой, тогда delete
действительно выпустит p
. Если он был первым, поскольку в списке инициализации сначала установлены оба указателя на 0
, и можно безопасно вызывать delete
для нулевого указателя, delete p
является безопасной операцией, если первый новый сбой.
Теперь на примере справа. Мы перенесли распределение ресурсов в список инициализации и, таким образом, используем функцию try, которая является единственным способом захвата исключения. Опять одна из новостей провалилась. Если второй новый сбой, delete p
освободит ресурс, выделенный в этом указателе. Но если это был первый новый сбой, то p
никогда не инициализировался, и вызов delete p
- неопределенное поведение.
Возвращаясь к моему свободному переводу, если бы вы использовали RAII и только один ресурс на объект, мы бы написали тип как:
struct test {
std::auto_ptr<type> p,q;
test() : p( new type ), q( new type )
{}
};
В этом модифицированном примере, поскольку мы используем RAII, нас не волнует исключение. Если первый new
бросает, ресурс не получен и ничего не происходит. Если второй бросок завершится неудачно, p
будет уничтожен, потому что p
был полностью построен до попытки второго new
(там есть точка последовательности), и ресурс будет освобожден.
Так что нам даже не нужно try
для управления ресурсами ... что оставляет нам другое использование, упомянутое Саттером: перевод исключения. В то время как мы должны выбросить конструктор, который потерпел неудачу, мы можем выбрать то, что мы выбросим. Мы могли бы решить, что мы хотим бросить заказное initialization_error
независимо от того, что было внутренней ошибкой в конструкции. Вот для чего можно использовать блок try функции:
struct test {
std::auto_ptr<type> p,q;
test() try : p( new type ), q( new type ) {
} catch ( ... ) {
throw initialization_error();
}
};