Есть ли предпочтительная идиома для имитации try/finally Java в C++?

делал Java в течение нескольких лет, поэтому не отслеживал C++. Имеет наконец-то предложение было добавлено в обработку исключений C++ в определении языка?

есть ли предпочтительная идиома, которая имитирует попытку Java / finally?

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

Я могу пиши:

try {
  // do something
} catch(...) {
  // alas, can't examine the exception
  // can only do cleanup code and perhaps rethrow, ala:
  throw;
}

ДОБАВЛЕНИЕ EDIT:

Я в конечном итоге принимает ответить имел больше всего голосов, т. е. деструкторы для очистки. Конечно, из моих собственных комментариев ясно, что я не совсем согласен с этим. Однако C++ - это то, что есть, и так далее приложение endeavor у меня есть в имейте в виду, я собираюсь более или менее бороться придерживаться общего сообщества практиковать. Я буду использовать классы шаблонов для оберните ресурсы, которые не уже быть деструктор класса (т. е. библиотека C ресурсы), тем самым наделяя их семантика деструктора.

НОВОЕ ДОБАВЛЕНИЕ EDIT:

Хм, вместо наконец-то затем закрытие функцию возможно? Закрытие совмещенное с Подход ScopeGuard (см. ответы ниже) был бы путь к выполнить очистку с произвольным действия и доступ к очистке контекст внешней области кода. Очистка может быть выполнена в стиле идиомы, который наблюдается в программировании Ruby, где они предоставляют блоки очистки при открытии ресурса. Не функция закрытия рассматривается для C++?

15 ответов


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

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

обновление: прямо из первых уст: почему C++ не предоставляет конструкцию "finally"?


мои $.02. Я много лет программировал на управляемых языках, таких как C# и Java, но был вынужден переключиться на C++ в целях скорости. Сначала я не мог поверить, как я должен был написать подпись метода дважды в заголовочном файле, а затем в файле cpp, и мне не понравилось, что не было блока finally, и никакой сбор мусора не означал отслеживания утечек памяти везде - черт возьми, мне это совсем не понравилось!


ответ C++ - RAII: деструктор объекта будет выполнен, когда они выйдут из области видимости. Будь то возвращение, исключение или что-то еще. Если вы обрабатываете исключение где-то еще, вы можете быть уверены, что все объекты от вызываемой функции до вашего обработчика будут правильно уничтожены, вызвав их деструктор. Они уберут за тобой.

читать http://en.wikipedia.org/wiki/Resource_acquisition_is_initialization


нет, наконец, не был добавлен в C++, и вряд ли он когда-либо будет добавлен.

то, как C++ использует конструктор/деструктор, делает необходимость в конечном итоге ненужной.
Если вы используете catch(...) для очистки, то вы не используете C++ должным образом. Код очистки должен быть в деструкторе.

хотя это не требование использовать его, C++ имеет исключение std::.
Принуждение разработчиков к наследованию от определенного класса для использования исключения противоречит держите его простой философией C++. Его также почему мы не требуем, чтобы все классы производные от Object.

читать: поддерживает ли C++ блоки "finally"? (И чем это РАИИ-я слышал о?)

использование finally более подвержено ошибкам,чем деструкторы для очистки.
Это связано с тем, что вы заставляете пользователя объекта выполнять очистку, а не дизайнера/реализатора класса.


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

если вся очистка всегда выполняется в деструкторы тогда не понадобятся. быть любым кодом очистки в catch block-Еще C++ имеет блоки catch, где действия очистки выполняются. Действительно это имеет блок для catch(...) где это только можно выполнить действия очистки (ну, конечно, не может добраться до любого сведения об исключении лесозаготовительный.)

catch имеет совершенно отдельную цель, и как программист Java вы должны знать об этом. Finally пункт для "безусловной" акциях. Независимо от того, как блок выходит, это должно быть сделано. Catch предназначен для условной очистки. Если этот тип исключения, мы должны выполнить несколько дополнительных действий.

очистка в блоке finally будет сделать ли исключение или нет - это чего всегда хочется, когда код очистки существует.

в самом деле? Если мы хотим этого всегда случается для этого типа (скажем, мы всегда хотим закрыть соединение с базой данных, когда мы закончим с ним), то почему бы нам не определить его после? В самом типе? Сделайте соединение с базой данных закрытым, а не помещать попробуйте/, наконец, вокруг каждого его использования?

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

разработчики C++ с первого дня страдает от необходимости повторной очистки действия, которые отображаются в блоках catch в поток кода, возникающий при успешный выход из блока try. Программисты Java и C# просто делают это однажды в наконец блок.

нет. Программистов на C++ это никогда не беспокоило. С программистов. И программисты на С, которые поняли, что у С++ есть классы, а затем назвали себя программистами на с++.

я программирую на C++ и C# ежедневно, и я чувствую, что меня преследует нелепая настойчивость C#, что я должен предоставить предложение finally (или using block) каждый раз, когда я использую соединение с базой данных или что-то еще, что должно быть очищено.

в C++ позволяет мне укажите раз и навсегда, что "всякий раз, когда мы закончим с этим типом, он должен выполнять эти действия". Я не рискую забыть освободить память. Я не рискую забыть закрыть дескрипторы файлов, сокеты или подключения к базе данных. Потому что моя память, мои дескрипторы, сокеты и соединения db делают это сами.

Как это когда-нибудь предпочтительнее писать дубликат кода очистки каждый раз, когда вы используете тип? Если вам нужно обернуть тип, потому что у него нет деструктора сам по себе, у вас есть два простых варианта:

  • ищите правильную библиотеку C++, которая предоставляет этот деструктор (подсказка: Boost)
  • используйте boost:: shared_ptr, чтобы обернуть его и предоставить ему пользовательский функтор во время выполнения, указав очистку.

при написании сервера приложений такие программы, как Java EE app servers Glassfish, JBoss, etc. вы хотите быть возможность поймать и зарегистрировать исключение информация - в отличие от пусть упасть на пол. Или хуже попасть в выполнения и привести к внезапной резкий выход сервера приложений. Вот почему очень желательно иметь общий базовый класс для любого возможное исключение. И у C++ есть именно такой класс. std::исключение.

сделали C++ С дней CFront и Java / C# большую часть этого десятилетия. Есть ясно видеть, что это просто огромный разрыв культуры как принципиально к подобным вещам подходят.

нет, вы никогда не делали C++. Вы сделали CFront или C с классами. Не C++. Это огромная разница. Перестаньте называть ответы хромыми, и вы можете узнать что-то о языке, который, как вам казалось, вы знаете. ;)


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

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

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

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

есть много проблем с C++, но это не один из них. Есть способы, в которых Java лучше, чем C++, но это не один из них.

Java было бы намного лучше с способом реализации РАИИ вместо попытки...в конечном счете.


чтобы избежать необходимости определять класс-оболочку для каждого освобождаемого ресурса, вас может заинтересовать 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);

пример того, как трудно использовать, наконец, правильно.

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

В C++

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 становится все труднее и труднее правильно использовать при наличии исключений.

использование, наконец, переносит ответственность за правильное использование на пользователя объекта. Используя механизм конструктора / деструктора, предоставляемый C++, вы перемещаете ответственность за правильное использование в конструктор / реализатор класса. Это inheritanly безопаснее, так как дизайнеру нужно сделать это правильно только один раз на уровне класса (вместо того, чтобы разные пользователи пытались сделать это правильно по-разному).


использование C++11 с его лямбда-выражения, Я недавно начал использовать следующий код, чтобы имитировать 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
}

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

немного странно, что вам нужно написать код для finally до код try блок, но кроме этого он на самом деле чувствует себя очень похоже на подлинный try/finally от Java. Я думаю, не следует злоупотреблять этим для ситуаций, когда объект с собственным собственным деструктором был бы более подходящим, но есть случаи, когда я рассматриваю этот подход над более подходящим. Я обсуждал один такой сценарий в этот вопрос.

насколько я понимаю,std::function<void()> будет использовать некоторую косвенность указателя и по крайней мере один вызов виртуальной функции для выполнения его стирания типа, то есть накладные расходы. Не используйте эту технику в узком цикле, где производительность имеет решающее значение. В этих случаях специализированный объект, деструктор которого делает только одно, будет больше соответствующий.


деструкторы C++ делают finally излишним. Вы можете получить тот же эффект, переместив код очистки из finally в соответствующие деструкторы.


Я думаю, что вы упускаете смысл того, что catch (...) можно сделать.

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

если вы знаете о некоторых исключениях или иерархиях исключений, с которыми вы можете что-то сделать, то это место для блоков catch с именами типов explicity.

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

как упоминалось в других ответах, если вы используете локальные объекты для управления ресурсами (RAII), то это может быть удивительно и разъяснение того, как мало блоков catch вам нужно, часто - если вам не нужно ничего делать локально с исключением - даже блок try может быть избыточным, поскольку вы позволяете исключениям вытекать в клиентский код, который может отвечать на них, все еще гарантируя отсутствие проблем с ресурсами.

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

class LocalFinallyReplacement {
    ~LocalFinallyReplacement() { /* Finally code goes here */ }
};
// ...
{ // some function...
    LocalFinallyReplacement lfr; // must be a named object

    // do something
}

обратите внимание, как мы можем полностью покончить с try, catch и throw.

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


Не completetely оффтоп.

очистка ресурсов DB покрытия котла в Java

режим сарказма: разве идиома Java не прекрасна?


Я сделал много дизайна класса и дизайна обертки шаблона на C++ за эти 15 лет и сделал все это на C++ с точки зрения очистки деструкторов. Однако каждый проект также неизменно включал использование библиотек C, которые предоставляли ресурсы с моделью использования open it, use it, close it. Попытка/, наконец, означала бы, что такой ресурс можно просто потреблять там, где он должен быть - полностью надежным способом - и делать с ним. Наименее утомительный подход к программированию, который ситуация. Можно было бы справиться со всем другим состоянием, происходящим во время логики этой очистки, без необходимости удаляться в каком-либо деструкторе оболочки.

Я сделал большую часть моего c++ кодирования на Windows, так что всегда можно прибегнуть к использованию Microsoft __try/__наконец для таких ситуаций. (Их структурированная обработка исключений имеет некоторые мощные возможности для работы с исключениями.) Увы, не похоже, что язык C когда-либо ратифицировал какую-либо портативную обработку исключений строит.

Это не было идеальным решением, хотя, потому что было не просто смешать код C и C++ в блоке try, где любой стиль исключения может быть брошен. Блок finally, добавленный в C++, был бы полезен для этих ситуаций и позволил бы переносимость.


Что касается вашего добавления-edit, да, закрытие рассматривается для C++0x. Их можно использовать с предохранителями scoped RAII для того чтобы обеспечить легкое для использования разрешения, проверки Веб-журнал Pizer. Их также можно использовать для имитации try-finally, see ответ ; но это действительно хорошая идея ? .


думал, что я добавлю к этому свое собственное решение-своего рода смарт-обертку указателя, когда вам приходится иметь дело с типами без 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
    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:

/*  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();
        }
    }
};

у меня есть несколько различных видов releaser, как это, в том числе один бесплатно() и один для CloseHandle().