Есть ли благоприятная идиома для устранения Java try/finally на С++?
Выполнял Java в течение нескольких лет, поэтому не отслеживал С++. Добавлен ли параметр finally в обработку исключений С++ в определении языка?
Есть ли предпочтительная идиома, которая имитирует Java try/finally?
Am также беспокоился о том, что С++ не имеет высшего супер-типа для всех возможных исключений, которые могут быть выбраны как класс Java Throwable.
Я могу написать:
try {
// do something
} catch(...) {
// alas, can't examine the exception
// can only do cleanup code and perhaps rethrow, ala:
throw;
}
ADDENDUM EDIT:
Я закончил тем, что принял ответ, который имели самые высокие голоса, то есть использовать деструкторы для очистки. Конечно, из моих собственных комментариев, ясно, что я не согласны с этим. Однако С++ - это то, что есть, и поэтому в приложение, которое у меня есть ум, я собираюсь более или менее стремиться придерживаться общего сообщества практика. Я буду использовать классы шаблонов для обернуть ресурсы, которые еще не деструктор класса (т.е. библиотека C ресурсы), тем самым отдавая им семантика деструктора.
NEW ADDENDUM EDIT:
Хмм, вместо наконец, тогда замыкание возможно, функция? Закрытие в сочетании с Подход ScopeGuard (см. Один из ответы ниже) будет способом выполнить очистку с произвольными действия и доступ к очистке контекст внешней области кода. Очистка может выполняться в стиле идиомы, который рассматривается в программировании Ruby, где они поставляют блоки очистки при открытии ресурса. Это не функция закрытия, рассматриваемая для С++?
Ответы
Ответ 1
Эффективное использование деструкторов. Когда исключение выбрано в блоке try, любой объект, созданный внутри него, будет немедленно уничтожен (и, следовательно, вызван его деструктор).
Это отличается от Java, где вы не знаете, когда будет вызываться финализатор объекта.
ОБНОВЛЕНИЕ: прямо из устья лошади: Почему С++ не предоставляет конструкцию "finally" ?
Ответ 2
Мои $0,02. Я программировал на управляемых языках, таких как С# и Java, в течение многих лет, но был вынужден переключиться на С++ для целей скорости. Сначала я не мог поверить, что мне пришлось дважды записывать подпись метода в файле заголовка, а затем в файле cpp, и мне не нравилось, как не было окончательного блока, и никакая сборка мусора не означала утечку памяти во всем мире - черт возьми, мне это совсем не понравилось!
Однако, как я уже сказал, я был вынужден использовать С++. Поэтому я был вынужден серьезно изучить его, и теперь я наконец понял все идиомы программирования, такие как RAII, и я получу все тонкости языка и т.д. Мне потребовалось некоторое время, но теперь я вижу, насколько отличается от языка его сравнение с С# или Java.
В эти дни я думаю, что С++ - лучший язык! Да, я могу понять, что есть немного больше того, что я иногда называю "мякиной" (казалось бы, ненужным материалом для написания), но после серьезного использования языка я полностью изменил свое мнение об этом.
У меня всегда были утечки памяти. Я использовал весь свой код в файле .h, потому что я ненавидел разделение кода, я не мог понять, почему они это сделают! И я всегда был в конечном итоге с глупыми циклическими включениями зависимостей и кучами. Я действительно повесил трубку на С# или Java, для меня С++ был огромным шагом вниз. В эти дни я это понимаю. У меня почти нет утечек памяти, мне нравится разделение интерфейса и реализации, и у меня больше нет проблем с зависимостями циклов.
И я тоже не пропустил блок finally. Честно говоря, я считаю, что эти программисты на С++, о которых вы говорите о написании повторяющихся действий по очистке в блоках catch, просто звучат для меня, как будто они просто плохие программисты на С++. Я имею в виду, что это не похоже на то, что любой из других программистов на С++ в этом потоке имеет какие-либо проблемы, о которых вы говорите. RAII действительно делает окончательно избыточным, и, во всяком случае, это меньше работает. Вы пишете один деструктор, и тогда вам никогда не придется писать еще один раз, когда-либо! Ну, по крайней мере, для этого типа.
С уважением, я думаю, что вы сейчас используете Java, как и я.
Ответ 3
Ответ на С++ - это RAII: деструктор объекта будет выполняться, когда он выходит из области видимости. Ли по возвращении, за исключением или что-то еще. Если вы обрабатываете исключение в другом месте, вы можете быть уверены, что все объекты от вызываемой функции до вашего обработчика будут правильно разрушены, вызвав их деструктор. Они уберут вас.
Прочитайте http://en.wikipedia.org/wiki/Resource_acquisition_is_initialization
Ответ 4
Наконец, на С++ не добавлено, и он никогда не будет добавлен.
Способ, которым С++ использует конструктор/деструктор, делает ненужным необходимость.
Если вы используете catch (...) для очистки, вы не используете С++ правильно. Код очистки должен быть в деструкторе.
Хотя это не требование, чтобы использовать его, на С++ есть исключение std::.
Принуждение разработчиков к определенному классу использовать исключение идет вразрез с его простой философией С++. Его также почему мы не требуем, чтобы все классы извлекались из Object.
Читайте: Поддерживает ли С++ "наконец" блоки? (И что это "RAII" , о котором я все время слышу?)
Использование, наконец, более подвержено ошибкам, чем деструкторы для очистки.
Это связано с тем, что вы вынуждаете пользователя объекта выполнять очистку, а не конструктор/исполнитель класса.
Ответ 5
Хорошо, я должен добавить ответ на те вопросы, которые вы сделали в отдельном ответе:
(Было бы намного удобнее, если бы вы отредактировали это в исходном вопросе, так что это не заканчивается внизу ниже ответов на него.
Если вся очистка всегда выполняется в деструкторам тогда не понадобится быть любым кодом очистки в catch block - все же С++ имеет блоки catch, где делаются действия по очистке. Действительно, это имеет блок для catch (...), где он только выполнять действия по очистке (ну, конечно, нельзя информацию об исключениях для любого каротаж).
catch имеет совершенно отдельную цель, и, как программист на Java, вы должны знать об этом. Предложение finally для "безусловных" действий очистки. Независимо от того, как выйдет блок, это должно быть сделано. Поймать для условной очистки. Если этот тип исключения выбрасывается, нам нужно выполнить несколько дополнительных действий.
Очистка в блоке finally будет сделайте, было ли исключение или нет - это что всегда хочется, когда код очистки существует.
Действительно? Если мы хотим, чтобы это всегда происходило для этого типа (скажем, мы всегда хотим закрыть соединение с базой данных, когда мы закончим с ним), то почему бы нам не определить его один раз? В самом типе? Сделать соединение с базой данных закрытым, вместо того, чтобы ставить try/finally вокруг каждого его использования?
Это точка в деструкторах. Они гарантируют, что каждый тип может позаботиться о своей собственной очистке, каждый раз, когда он будет использоваться, без того, чтобы вызывающий должен был подумать об этом.
Разработчики С++ с первого дня были страдает от необходимости повторной очистки действия, которые появляются в блоках catch в поток кода, который возникает при успешный выход из блока try. Java и программисты на С# просто делают это один раз в блоке finally.
Нет. Программисты на C++ никогда не страдали от этого. C программисты. И программисты C, которые поняли, что С++ имеют классы, а затем называли себя программистами на С++.
Я программирую ежедневно на С++ и С#, и я чувствую, что меня мучает С# смешная настойчивость в том, что я должен предоставить предложение finally (или блок using
) КАЖДОЕ ОДИНОЧНОЕ ВРЕМЯ Я использую соединение с базой данных или что-то еще, что очиститься.
С++ позволяет мне указать раз и навсегда, что "всякий раз, когда мы закончили с этим типом, он должен выполнять эти действия". Я не рискну забыть выпустить память. Я не рискну забыть закрыть файловые дескрипторы, сокеты или соединения с базой данных. Потому что моя память, мои ручки, сокеты и соединения db делают это сами.
Как может быть предпочтительнее писать повторяющийся код очистки каждый раз, когда вы используете тип? Если вам нужно обернуть тип, потому что у него нет самого деструктора, у вас есть два простых варианта:
- Найдите подходящую библиотеку С++, которая предоставляет этот деструктор (подсказка: Boost)
- Используйте boost:: shared_ptr, чтобы его обернуть, и поставьте его с помощью специального функтора во время выполнения, указав очистку, которая будет выполнена.
Когда вы пишете сервер приложений программное обеспечение, такое как серверы приложений Java EE Glassfish, JBoss и т.д., Вы хотите быть возможность ловить и регистрировать исключение информации - в отличие от этого падают на пол. Или хуже попасть в время выполнения и вызывают неуважение внезапный выход сервера приложений. Вот почему очень желательно иметь базовый базовый класс для любого возможное исключение. И С++ имеет такой класс. станд:: исключение.
Сделали С++ с дней CFront и Java/С# в течение большей части этого десятилетия. Является ясно видеть там просто огромную культурный разрыв в том, как принципиально схожие вещи приближаются.
Нет, вы никогда не делали С++. Вы сделали CFront или C с классами. Не С++. Там огромная разница. Прекратите называть ответы хромыми, и вы можете узнать что-то о языке, который, как вы считали, вы знаете.;)
Ответ 6
Функции очистки сами по себе являются полностью хромыми. Они имеют низкую сплоченность, поскольку они должны выполнять серию мероприятий, связанных только с тем, когда они происходят. Они имеют высокое сцепление, поскольку им необходимо изменить внутреннюю структуру, когда функции, которые на самом деле что-то делают, изменены. Из-за этого они подвержены ошибкам.
Конструкция try... finally - это основа для функций очистки. Это язык, способный писать паршивый код. Более того, поскольку он рекомендует писать один и тот же код очистки снова и снова, он подрывает принцип DRY.
Для этих целей предпочтительнее использовать С++. Код очистки для ресурса записывается ровно один раз в деструкторе. Он в том же месте, что и остальная часть кода для этого ресурса, и поэтому имеет хорошую связность. Код очистки не должен быть помещен в несвязанные модули, и поэтому это сокращает сцепление. Он написан точно один раз, когда он хорошо разработан.
Более того, С++-путь намного более однородный. С++ с добавлением интеллектуальных указателей обрабатывает всевозможные ресурсы таким же образом, в то время как Java хорошо управляет памятью и предоставляет неадекватные конструкции для выпуска других ресурсов.
С С++ существует множество проблем, но это не один из них. Есть способы, в которых Java лучше, чем С++, но это не один из них.
Java будет намного лучше с возможностью реализовать RAII вместо try... наконец.
Ответ 7
Чтобы избежать необходимости определять класс-оболочку для каждого освобождаемого ресурса, вам может быть интересен ScopeGuard (http://www.ddj.com/cpp/184403758), который позволяет создавать "чистящие средства" на лету.
Например:
FILE* fp = SomeExternalFunction();
// Will automatically call fclose(fp) when going out of scope
ScopeGuard file_guard = MakeGuard(fclose, fp);
Ответ 8
Пример того, как трудно это использовать, наконец, правильно.
Открыть и закрыть два файла.
Где вы хотите гарантировать, что файл будет закрыт правильно.
Ожидание GC не является вариантом, так как файлы могут быть повторно использованы.
В С++
void foo()
{
std::ifstream data("plop");
std::ofstream output("plep");
// DO STUFF
// Files closed auto-magically
}
На языке без деструкторов, но имеет предложение finally.
void foo()
{
File data("plop");
File output("plep");
try
{
// DO STUFF
}
finally
{
// Must guarantee that both files are closed.
try {data.close();} catch(Throwable e){/*Ignore*/}
try {output.close();}catch(Throwable e){/*Ignore*/}
}
}
Это простой пример, и код уже запутан. Здесь мы только пытаемся объединить 2 простых ресурса. Но по мере увеличения количества ресурсов, которые необходимо контролировать, и/или их сложности увеличивается, использование блока finally становится все труднее и труднее использовать при наличии исключений.
Использование, наконец, повышает ответственность за правильное использование пользователя. Используя механизм конструктора/деструктора, предоставляемый С++, вы переносите ответственность правильного использования разработчику/разработчику этого класса. Это наследуется безопаснее, так как разработчику нужно сделать это правильно только один раз на уровне класса (вместо того, чтобы разные пользователи пытались и делали это правильно по-разному).
Ответ 9
Используя С++ 11 с лямбда-выражениями, я недавно начал использовать следующий код для mimic finally
:
class FinallyGuard {
private:
std::function<void()> f_;
public:
FinallyGuard(std::function<void()> f) : f_(f) { }
~FinallyGuard() { f_(); }
};
void foo() {
// Code before the try/finally goes here
{ // Open a new scope for the try/finally
FinallyGuard signalEndGuard([&]{
// Code for the finally block goes here
});
// Code for the try block goes here
} // End scope, will call destructor of FinallyGuard
// Code after the try/finally goes here
}
FinallyGuard
- это объект, который сконструирован с вызываемым функционально подобным аргументом, предпочтительнее лямбда-выражением. Он будет просто помнить эту функцию до тех пор, пока не будет вызван ее деструктор, что происходит, когда объект выходит из области видимости, либо из-за нормального потока управления, либо из-за разворачивания стека во время обработки исключений. В обоих случаях деструктор вызовет функцию, выполняя соответствующий код.
Немного странно, что вам нужно написать код для finally
перед кодом для блока try
, но кроме этого он действительно чувствует себя как подлинный try
/finally
от Ява. Я думаю, что не следует злоупотреблять этим в ситуациях, когда объект со своим собственным деструктором будет более уместным, но бывают случаи, когда я считаю этот подход более подходящим. Я обсуждал один такой сценарий в этом вопросе.
Насколько я понимаю, std::function<void()>
будет использовать некоторую косвенность указателя и хотя бы один вызов виртуальной функции для выполнения стирания типа , поэтому будет накладные расходы на производительность. Не используйте эту технику в узком цикле, где критическая производительность. В этих случаях специализированный объект, деструктор которого делает только одно, будет более подходящим.
Ответ 10
Деструкторы С++ делают finally
избыточным. Вы можете получить тот же эффект, перемещая код очистки от окончательно до соответствующих деструкторов.
Ответ 11
Я думаю, что вам не хватает того, что может сделать catch (...)
.
Вы говорите в своем примере "Увы, не можете изучить исключение". Ну, у вас нет информации о типе исключения. Вы даже не знаете, является ли он полиморфным типом, даже если у вас была какая-то нетипизированная ссылка на него, вы даже не могли бы безопасно попробовать dynamic_cast
.
Если вы знаете об определенных исключениях или иерархиях исключений, которые вы можете что-то сделать, тогда это место для блоков catch с явно выраженными именами.
catch (...)
не всегда полезен в С++. Его можно использовать в местах, которые должны гарантировать, что они не бросают, или бросать только определенные сокращенные исключения. Если вы используете catch (...)
для очистки, то есть очень хороший шанс, что ваш код не будет безопасным исключением в любом случае.
Как упоминалось в других ответах, если вы используете локальные объекты для управления ресурсами (RAII), то это может быть удивительно и интересно, как мало блоков блокирования вам нужно, часто - если вам не нужно ничего делать локально с исключением - даже блок try может быть избыточным, поскольку вы позволяете исключениям выходить из клиентского кода, который может реагировать на них, сохраняя при этом отсутствие проблем с ресурсами.
Чтобы ответить на ваш первоначальный вопрос, если вам нужен какой-то фрагмент кода для запуска в конце блока, исключение или исключение, тогда будет рецепт.
class LocalFinallyReplacement {
~LocalFinallyReplacement() { /* Finally code goes here */ }
};
// ...
{ // some function...
LocalFinallyReplacement lfr; // must be a named object
// do something
}
Обратите внимание, что мы можем полностью избавиться от try
, catch
и throw
.
Если у вас были данные в функции, которая была первоначально объявлена за пределами блока try, к которой вам нужен доступ, в блоке finally, вам может потребоваться добавить это к конструктору вспомогательного класса и сохранить его до тех пор, пока деструктор, Однако в этот момент я серьезно пересмотрю, может ли проблема быть решена путем изменения дизайна объектов обработки локальных ресурсов, поскольку это будет означать что-то в дизайне.
Ответ 12
Не полностью offtopic.
Очистка ресурсов БД в байте в Java
Режим сарказма: разве замечательная Java-идиома?
Ответ 13
В течение этих 15 лет я сделал много дизайна дизайна и дизайна шаблонов на С++, и сделал все это на С++ в плане очистки деструкторов. Однако каждый проект также неизменно включал использование библиотек C, которые предоставляли ресурсы с открытым им, использовали его, закрывали его модель использования. Попытка/в конечном итоге означала бы, что такой ресурс может быть просто потреблен там, где он должен быть - полностью надежным способом - и быть с ним связан. Наименее скучный подход к программированию этой ситуации. Может иметь дело со всем другим состоянием, происходящим во время логики этой очистки, без необходимости убираться в какой-нибудь деструктор оболочки.
Я сделал большую часть своего кода на С++ в Windows, поэтому всегда мог прибегнуть к использованию Microsoft __try/__ для таких ситуаций. (Их структурированная обработка исключений обладает некоторыми мощными способностями для взаимодействия с исключениями.) Увы, не похоже, что язык C когда-либо ратифицировал любые переносимые конструкторы обработки исключений.
Это было не идеальное решение, потому что не было простого сочетания кода C и С++ в блоке try, где может возникнуть любой стиль исключения. Наконец, блок, добавленный в С++, был бы полезен для этих ситуаций и обеспечил бы переносимость.
Ответ 14
Что касается вашего добавления-редактирования, то закрытие рассматривается для С++ 0x. Они могут использоваться с защитными ограждениями RAII для обеспечения простого в использовании решения, проверьте веб-журнал Pizer. Они также могут использоваться для имитации try-finally, см. этот ответ; но это действительно хорошая идея?.
Ответ 15
Думаю, что я добавлю свое собственное решение - своего рода оболочку смарт-указателя, когда вам придется иметь дело с типами, отличными от RAII.
Используется следующим образом:
Finaliser< IMAPITable, Releaser > contentsTable;
// now contentsTable can be used as if it were of type IMAPITable*,
// but will be automatically released when it goes out of scope.
Итак, здесь реализация Finaliser:
/* Finaliser
Wrap an object and run some action on it when it runs out of scope.
(A kind of 'finally.')
* T: type of wrapped object.
* R: type of a 'releaser' (class providing static void release( T* object )). */
template< class T, class R >
class Finaliser
{
private:
T* object_;
public:
explicit Finaliser( T* object = NULL )
{
object_ = object;
}
~Finaliser() throw()
{
release();
}
Finaliser< T, R >& operator=( T* object )
{
if (object_ != object && object_ != NULL)
{
release();
}
object_ = object;
return *this;
}
T* operator->() const
{
return object_;
}
T** operator&()
{
return &object_;
}
operator T*()
{
return object_;
}
private:
void release() throw()
{
R::release< T >( object_ );
}
};
... и здесь Выжимщик:
/* Releaser
Calls Release() on the object (for use with Finaliser). */
class Releaser
{
public:
template< class T > static void release( T* object )
{
if (object != NULL)
{
object->Release();
}
}
};
У меня есть несколько различных типов релизов, таких как, например, один для free() и один для CloseHandle().