Нужно попробовать поймать в конструкторе - PullRequest
3 голосов
/ 18 августа 2011

Ссылка http://gotw.ca/gotw/066.htm гласит, что

Мораль # 1: у обработчиков функции try-block конструктора есть только одна цель - перевести исключение.(И, возможно, для ведения журнала или некоторых других побочных эффектов.) Они бесполезны для каких-либо других целей.

Хотя http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.8

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

Разве эти 2 утверждения не противоречат друг другу?Первый тип подразумевает, что try-catch внутри конструктора практически бесполезен, а второй говорит, что он необходим для освобождения ресурсов.Что мне здесь не хватает?

Ответы [ 5 ]

2 голосов
/ 18 августа 2011

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();
   }
};
2 голосов
/ 18 августа 2011

Мораль # 1 говорит о function-try-block , а второе утверждение говорит о нормальном блоке try catch , оба явно различаются.

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

2 голосов
/ 18 августа 2011

Они относятся к разным вещам.

Первый относится к блоку function- try, то есть к блоку try, который включает в себя целую функцию и, в случае конструкторов, включает в себя также вызовы конструкторов базовых классов и объекты-члены, т.е. вещи, которые выполняются до запуска фактического тела конструктора.

Мораль заключается в том, что, если базовый класс или член класса не может сработать правильно, конструкция объекта должна завершиться с ошибкой с исключением, поскольку в противном случае вновь созданный объект остается в несогласованном состоянии, с базовый объект / члены-объекты наполовину сконструированы. Таким образом, цель такого try блока должна состоять только в том, чтобы перевести / отбросить такое исключение, возможно, записав инцидент. Вы не можете сделать это каким-либо другим способом: если вы явно не throw внутри вашего catch, компилятор добавляет неявное throw;, чтобы предотвратить возможность "полу-построенного объекта".

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

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

1 голос
/ 18 августа 2011

Во-первых, функция-try-block конструктора отличается от try-catch внутри конструктора.

Во-вторых, говорить «вещи, которые необходимо отменить, должен помнить элемент данных» - это не то же самое, что говорить «использовать блоки try-catch для отмены вещей».

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

1 голос
/ 18 августа 2011

Разве эти 2 утверждения не противоречат друг другу?

Нет.Второй в основном означает, что если конструктор выдает исключение, которое выходит из него (т.е. конструктор), то деструктор не вызывается.И первое означает, что функция-try-block не позволяет исключению выйти из конструктора.Он ловит его внутри самого конструктора и обрабатывает его прямо там.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...