Ответ 1

Нет, С++ не поддерживает блоки "finally". Причина в том, что С++ вместо этого поддерживает RAII: "Инициализация ресурсов - это Инициализация ресурсов" - плохое имя для действительно полезного понятия.

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

Общим для RAII использованием является блокировка мьютекса:

// A class with implements RAII
class lock
{
    mutex &m_;

public:
    lock(mutex &m)
      : m_(m)
    {
        m.acquire();
    }
    ~lock()
    {
        m_.release();
    }
};

// A class which uses 'mutex' and 'lock' objects
class foo
{
    mutex mutex_; // mutex for locking 'foo' object
public:
    void bar()
    {
        lock scopeLock(mutex_); // lock object.

        foobar(); // an operation which may throw an exception

        // scopeLock will be destructed even if an exception
        // occurs, which will release the mutex and allow
        // other functions to lock the object and run.
    }
};

RAII также упрощает использование объектов в качестве членов других классов. Когда владелец класса "разрушен", ресурс, управляемый классом RAII, освобождается, потому что в результате вызывается деструктор для управляемого RAII класса. Это означает, что когда вы используете RAII для всех членов класса, которые управляют ресурсами, вы можете избавиться от использования очень простого, возможно даже деструктора по умолчанию для класса владельца, поскольку ему не нужно вручную управлять временем жизни ресурса члена, (Спасибо Майку Б. за указание на это.)

Для тех, кто знаком с С# или VB.NET, вы можете признать, что RAII похож на Детерминированное уничтожение .NET с использованием операторов IDisposable и 'using'. Действительно, эти два метода очень похожи. Основное различие заключается в том, что RAII детерминистически выделяет любой тип ресурсов, включая память. При реализации IDisposable в .NET(даже на языке .NET С++/CLI) ресурсы будут детерминистически выпущены, за исключением памяти. В .NET память не детерминирована; память освобождается только во время циклов сбора мусора.

 

† Некоторые люди считают, что "Уничтожение - это отказ от ресурсов" - более точное имя для идиомы RAII.

Ответ 2

В С++, наконец, требуется NOT из-за RAII.

RAII переносит ответственность безопасности исключений от пользователя объекта к конструктору (и разработчику) объекта. Я бы сказал, что это правильное место, так как тогда вам нужно только один раз получить исключение безопасности (в проекте/реализации). Используя, наконец, вы должны получить исключение безопасности правильно каждый раз, когда вы используете объект.

Также ИМО код выглядит аккуратно (см. ниже).

Пример:

Объект базы данных. Чтобы убедиться, что используется соединение с БД, его необходимо открыть и закрыть. Используя RAII, это можно сделать в конструкторе/деструкторе.

С++ Как RAII

void someFunc()
{
    DB    db("DBDesciptionString");
    // Use the db object.

} // db goes out of scope and destructor closes the connection.
  // This happens even in the presence of exceptions.

Использование RAII делает использование объекта DB правильно очень простым. Объект DB будет правильно закрывать себя с помощью деструктора, независимо от того, как мы пытаемся его использовать.

Java, как, наконец,

void someFunc()
{
    DB      db = new DB("DBDesciptionString");
    try
    {
        // Use the db object.
    }
    finally
    {
        // Can not rely on finaliser.
        // So we must explicitly close the connection.
        try
        {
            db.close();
        }
        catch(Throwable e)
        {
           /* Ignore */
           // Make sure not to throw exception if one is already propagating.
        }
    }
}

При использовании, наконец, правильное использование объекта делегируется пользователю объекта. i.e. Пользователь объекта должен правильно закрыть соединение с БД. Теперь вы можете утверждать, что это можно сделать в финализаторе, но ресурсы могут иметь ограниченную доступность или другие ограничения, и поэтому вы, как правило, хотите контролировать выпуск объекта и не полагаться на не детерминированное поведение сборщика мусора.

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

Более подробный анализ можно найти здесь: http://accu.org/index.php/journals/236

Ответ 3

В С++ 11, если необходимо, RAII позволяет сделать окончательно:

namespace detail { //adapt to your "private" namespace
template <typename F>
struct FinalAction {
    FinalAction(F f) : clean_{f} {}
   ~FinalAction() { if(enabled_) clean_(); }
    void disable() { enabled_ = false; };
  private:
    F clean_;
    bool enabled_{true}; }; }

template <typename F>
detail::FinalAction<F> finally(F f) {
    return detail::FinalAction<F>(f); }

пример использования:

#include <iostream>
int main() {
    int* a = new int;
    auto delete_a = finally([a] { delete a; std::cout << "leaving the block, deleting a!\n"; });
    std::cout << "doing something ...\n"; }

вывод будет:

doing something...
leaving the block, deleting a!

Лично я использовал это несколько раз, чтобы обеспечить закрытие дескриптора файла POSIX в программе на С++.

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

Кроме того, мне нравится это лучше, чем другие языки наконец, потому что, если использовать, естественно, вы пишете код закрытия рядом с открытым кодом (в моем примере новый и delete), а разрушение следует за построением в порядке LIFO, как обычно, в С++. Единственный недостаток заключается в том, что вы получаете автоматическую переменную, которую вы действительно не используете, а синтаксис лямбда делает ее немного шумной (в моем примере в четвертой строке только слово наконец и {} -block справа имеют смысл, остальное по существу является шумом).

Другой пример:

 [...]
 auto precision = std::cout.precision();
 auto set_precision_back = finally( [precision, &std::cout]() { std::cout << std::setprecision(precision); } );
 std::cout << std::setprecision(3);

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

отключить пример:

//strong guarantee
void copy_to_all(BIGobj const& a) {
    first_.push_back(a);
    auto undo_first_push = finally([first_&] { first_.pop_back(); });

    second_.push_back(a);
    auto undo_second_push = finally([second_&] { second_.pop_back(); });

    third_.push_back(a);
    //no necessary, put just to make easier to add containers in the future
    auto undo_third_push = finally([third_&] { third_.pop_back(); });

    undo_first_push.disable();
    undo_second_push.disable();
    undo_third_push.disable(); }

Ответ 4

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

Это означает, что когда вы достигаете RAII нирваны, и все члены класса используют RAII (например, умные указатели), вы можете уйти с очень простым (возможно, даже дефолтным) dtor для класса владельца, поскольку ему не нужно вручную управлять ресурсом ресурса члена.

Ответ 5

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

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

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

Однако...

RAII переносит ответственность безопасности исключений от пользователя объекта к дизайнеру

К сожалению, это его собственное падение. Старые привычки программирования C умирают тяжело. Когда вы используете библиотеку, написанную на C или очень стиле C, RAII не будет использоваться. За исключением повторной записи всего API-интерфейса, это то, с чем вам приходится работать. Затем отсутствие "наконец" действительно кусается.

Ответ 6

Извините за то, что выкалываете такой старый поток, но в следующих рассуждениях есть большая ошибка:

RAII переносит ответственность безопасности исключений от пользователя объекта к конструктору (и разработчику) объекта. Я бы сказал, что это правильное место, так как тогда вам нужно только один раз получить исключение безопасности (в проекте/реализации). Используя, наконец, вы должны получить исключение безопасности правильно каждый раз, когда вы используете объект.

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

void DoStuff(vector<string> input)
{
  list<Foo*> myList;

  try
  {    
    for (int i = 0; i < input.size(); ++i)
    {
      Foo* tmp = new Foo(input[i]);
      if (!tmp)
        throw;

      myList.push_back(tmp);
    }

    DoSomeStuff(myList);
  }
  finally
  {
    while (!myList.empty())
    {
      delete myList.back();
      myList.pop_back();
    }
  }
}

Конечно, сам список будет уничтожен при выходе из области видимости, но это не очистит созданные вами временные объекты.

Вместо этого вам нужно пройти уродливый маршрут:

void DoStuff(vector<string> input)
{
  list<Foo*> myList;

  try
  {    
    for (int i = 0; i < input.size(); ++i)
    {
      Foo* tmp = new Foo(input[i]);
      if (!tmp)
        throw;

      myList.push_back(tmp);
    }

    DoSomeStuff(myList);
  }
  catch(...)
  {
  }

  while (!myList.empty())
  {
    delete myList.back();
    myList.pop_back();
  }
}

Также: почему даже управляемые lanuages ​​предоставляют окончательный блок, несмотря на то, что ресурсы все равно освобождаются сборщиком мусора?

Подсказка: там вы можете больше "окончательно", чем просто освобождение памяти.

Ответ 7

FWIW, Microsoft Visual С++ действительно поддерживает попытку, и она исторически использовалась в приложениях MFC как метод поиска серьезных исключений, которые в противном случае привели бы к сбою. Например:

int CMyApp::Run() 
{
    __try
    {
        int i = CWinApp::Run();
        m_Exitok = MAGIC_EXIT_NO;
        return i;
    }
    __finally
    {
        if (m_Exitok != MAGIC_EXIT_NO)
            FaultHandler();
    }
}

Я использовал это в прошлом, чтобы делать такие вещи, как сохранять резервные копии открытых файлов перед выходом. Однако некоторые параметры отладки JIT будут разрушать этот механизм.

Ответ 8

Еще одна эмуляция блока finally с использованием лямбда-функций С++ 11

template <typename TCode, typename TFinallyCode>
inline void with_finally(const TCode &code, const TFinallyCode &finally_code)
{
    try
    {
        code();
    }
    catch (...)
    {
        try
        {
            finally_code();
        }
        catch (...) // Maybe stupid check that finally_code mustn't throw.
        {
            std::terminate();
        }
        throw;
    }
    finally_code();
}

Давайте надеяться, что компилятор оптимизирует код выше.

Теперь мы можем написать код так:

with_finally(
    [&]()
    {
        try
        {
            // Doing some stuff that may throw an exception
        }
        catch (const exception1 &)
        {
            // Handling first class of exceptions
        }
        catch (const exception2 &)
        {
            // Handling another class of exceptions
        }
        // Some classes of exceptions can be still unhandled
    },
    [&]() // finally
    {
        // This code will be executed in all three cases:
        //   1) exception was not thrown at all
        //   2) exception was handled by one of the "catch" blocks above
        //   3) exception was not handled by any of the "catch" block above
    }
);

Если вы хотите, вы можете обернуть эту идиому в макрос "попробуй - наконец":

// Please never throw exception below. It is needed to avoid a compilation error
// in the case when we use "begin_try ... finally" without any "catch" block.
class never_thrown_exception {};

#define begin_try    with_finally([&](){ try
#define finally      catch(never_thrown_exception){throw;} },[&]()
#define end_try      ) // sorry for "pascalish" style :(

Теперь блок "finally" доступен в С++ 11:

begin_try
{
    // A code that may throw
}
catch (const some_exception &)
{
    // Handling some exceptions
}
finally
{
    // A code that is always executed
}
end_try; // Sorry again for this ugly thing

Лично мне не нравится "макро" версия идиомы finally, и я бы предпочел использовать чистую функцию with_finally, хотя синтаксис в этом случае более громоздкий.

Вы можете проверить код выше здесь: http://coliru.stacked-crooked.com/a/1d88f64cb27b3813

PS

Если вам нужен блок finally в вашем коде, то, вероятно, лучше подойдут охранники с ограничениями и макросы ON_FINALLY/ON_EXCEPTION.

Вот краткий пример использования ON_FINALLY/ON_EXCEPTION:

void function(std::vector<const char*> &vector)
{
    int *arr1 = (int*)malloc(800*sizeof(int));
    if (!arr1) { throw "cannot malloc arr1"; }
    ON_FINALLY({ free(arr1); });

    int *arr2 = (int*)malloc(900*sizeof(int));
    if (!arr2) { throw "cannot malloc arr2"; }
    ON_FINALLY({ free(arr2); });

    vector.push_back("good");
    ON_EXCEPTION({ vector.pop_back(); });

    ...

Ответ 9

Как указано в других ответах, C++ может finally поддерживать -like функциональность. Реализация этой функциональности, которая, вероятно, наиболее близка к тому, чтобы быть частью стандартного языка, - это та, которая сопровождает основные руководящие принципы C++, набор передовых методов использования C++, отредактированный Бьярном Стоуструпом и Хербом Саттером. Реализация finally является частью библиотеки поддержки руководящих принципов (GSL). Повсюду в Руководстве рекомендуется использовать finally при работе с интерфейсами старого стиля, и у него также есть свое собственное руководство под названием Использовать объект final_action для выражения очистки, если нет подходящего дескриптора ресурса.

Таким образом, C++ не только finally поддерживает, но и рекомендуется использовать его во многих типичных случаях.

Пример использования реализации GSL будет выглядеть так:

#include <gsl/gsl_util.h>

void example()
{
    int handle = get_some_resource();
    auto handle_clean = gsl::finally([&handle] { clean_that_resource(handle); });

    // Do a lot of stuff, return early and throw exceptions.
    // clean_that_resource will always get called.
}

Внедрение и использование GSL очень похоже на ответ в Paolo.Bolzoni. Одно из отличий состоит в том, что объект, созданный gsl::finally() не имеет вызова disable(). Если вам нужна эта функциональность (скажем, для возврата ресурса после его сборки и исключений не должно быть), вы можете предпочесть реализацию Paolo. В противном случае использование GSL настолько близко к использованию стандартизированных функций, насколько это возможно.

Ответ 10

Не совсем, но вы можете имитировать их до некоторой степени, например:

int * array = new int[10000000];
try {
  // Some code that can throw exceptions
  // ...
  throw std::exception();
  // ...
} catch (...) {
  // The finally-block (if an exception is thrown)
  delete[] array;
  // re-throw the exception.
  throw; 
}
// The finally-block (if no exception was thrown)
delete[] array;

Обратите внимание, что finally-блок может сам вызвать исключение, прежде чем исходное исключение будет повторно выбрано, тем самым отбросив исходное исключение. Это то же самое поведение, что и в Java finally-block. Кроме того, вы не можете использовать return внутри блоков try & catch.

Ответ 11

Я придумал макрос finally, который можно использовать почти как ¹ ключевое слово finally в Java; он использует std::exception_ptr и друзей, лямбда-функции и std::promise, поэтому для этого требуется C++11 или выше; он также использует выражение составное выражение оператора Расширение GCC, которое также поддерживается clang.

ПРЕДУПРЕЖДЕНИЕ: более ранняя версия этого ответа использовала другую реализацию концепции со многими другими ограничениями.

Сначала определим вспомогательный класс.

#include <future>

template <typename Fun>
class FinallyHelper {
    template <typename T> struct TypeWrapper {};
    using Return = typename std::result_of<Fun()>::type;

public:    
    FinallyHelper(Fun body) {
        try {
            execute(TypeWrapper<Return>(), body);
        }
        catch(...) {
            m_promise.set_exception(std::current_exception());
        }
    }

    Return get() {
        return m_promise.get_future().get();
    }

private:
    template <typename T>
    void execute(T, Fun body) {
        m_promise.set_value(body());
    }

    void execute(TypeWrapper<void>, Fun body) {
        body();
    }

    std::promise<Return> m_promise;
};

template <typename Fun>
FinallyHelper<Fun> make_finally_helper(Fun body) {
    return FinallyHelper<Fun>(body);
}

Тогда есть фактический макрос.

#define try_with_finally for(auto __finally_helper = make_finally_helper([&] { try 
#define finally });                         \
        true;                               \
        ({return __finally_helper.get();})) \
/***/

Его можно использовать следующим образом:

void test() {
    try_with_finally {
        raise_exception();
    }    

    catch(const my_exception1&) {
        /*...*/
    }

    catch(const my_exception2&) {
        /*...*/
    }

    finally {
        clean_it_all_up();
    }    
}

Использование std::promise делает его очень простым в реализации, но, вероятно, также вводит довольно много ненужных служебных данных, которых можно избежать, переопределяя только необходимые функции из std::promise.


¹ CAVEAT: есть несколько вещей, которые не работают как java-версия finally. Сверху моей головы:

  • невозможно выйти из внешнего цикла с помощью оператора break из блоков try и catch(), поскольку они живут в лямбда-функции;
  • после try должен быть хотя бы один блок catch(): это требование С++;
  • если функция имеет возвращаемое значение, отличное от void, но нет возврата внутри блоков try и catch()'s, компиляция завершится неудачно, потому что макрос finally будет расширяться до кода, который захочет вернуть void, Это может быть ошибочно, если избежать макроса finally_noreturn.

В общем, я не знаю, буду ли я когда-либо использовать этот материал сам, но с ним было весело играть.:)

Ответ 12

У меня есть прецедент, где я думаю, что finally должен быть вполне приемлемой частью языка С++ 11, так как я думаю, что его легче читать с точки зрения потока. Мой вариант использования - цепочка потоков потребителей/производителей, где в конце прогона отправляется часовое nullptr, чтобы закрыть все потоки.

Если С++ поддерживает его, вы хотите, чтобы ваш код выглядел следующим образом:

    extern Queue downstream, upstream;

    int Example()
    {
        try
        {
           while(!ExitRequested())
           {
             X* x = upstream.pop();
             if (!x) break;
             x->doSomething();
             downstream.push(x);
           } 
        }
        finally { 
            downstream.push(nullptr);
        }
    }

Я думаю, что это более логично, что вы добавляете свое объявление finally в начале цикла, поскольку оно возникает после выхода цикла... но это желаемое за действительное, потому что мы не можем сделать это на С++. Обратите внимание, что очередь downstream подключена к другому потоку, поэтому вы не можете вставить дозорный элемент push(nullptr) в деструктор downstream, потому что он не может быть уничтожен на этом этапе... он должен остаться в живых пока другой поток не получит nullptr.

Итак, вот как использовать класс RAII с lambda, чтобы сделать то же самое:

    class Finally
    {
    public:

        Finally(std::function<void(void)> callback) : callback_(callback)
        {
        }
        ~Finally()
        {
            callback_();
        }
        std::function<void(void)> callback_;
    };

и вот как вы его используете:

    extern Queue downstream, upstream;

    int Example()
    {
        Finally atEnd([](){ 
           downstream.push(nullptr);
        });
        while(!ExitRequested())
        {
           X* x = upstream.pop();
           if (!x) break;
           x->doSomething();
           downstream.push(x);
        }
    }

Ответ 13

Как заявили многие, решение должно использовать возможности С++ 11, чтобы избежать блоков finally. Одна из особенностей - unique_ptr.

Здесь написан ответ Mephane, написанный с использованием шаблонов RAII.

#include <vector>
#include <memory>
#include <list>
using namespace std;

class Foo
{
 ...
};

void DoStuff(vector<string> input)
{
    list<unique_ptr<Foo> > myList;

    for (int i = 0; i < input.size(); ++i)
    {
      myList.push_back(unique_ptr<Foo>(new Foo(input[i])));
    }

    DoSomeStuff(myList);
}

Несколько дополнительных сведений об использовании unique_ptr с контейнерами стандартной библиотеки С++ здесь

Ответ 14

Я хотел бы предоставить альтернативу.

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

try{
   // something that might throw exception
} catch( ... ){
   // what to do with uknown exception
}

//final code to be called always,
//don't forget that it might throw some exception too
doSomeCleanUp(); 

Если вы хотите, наконец, блокировать последнее, что нужно сделать, когда выкидывается какое-либо исключение, вы можете использовать логическую переменную boolean - перед запуском вы установите значение false и поставьте истинное назначение в самом конце блока try, затем после проверки блока catch для значения переменной:

bool generalAppState = false;
try{
   // something that might throw exception

   //the very end of try block:
   generalAppState = true;
} catch( ... ){
   // what to do with uknown exception
}

//final code to be called only when exception was thrown,
//don't forget that it might throw some exception too
if( !generalAppState ){
   doSomeCleanUpOfDirtyEnd();
}

//final code to be called only when no exception is thrown
//don't forget that it might throw some exception too
else{
   cleanEnd();
}

Ответ 15

Я также думаю, что RIIA не является полностью полезной заменой для обработки исключений и наличия наконец. Кстати, я также думаю, что RIIA это плохое имя во всем мире. Я называю эти типы классов "уборщиками" и использую их МНОГО. В 95% случаев они не инициализируют и не получают ресурсы, они применяют некоторые изменения по определенным областям или принимают что-то уже настроенное и проверяют, уничтожено ли оно. Это официальное название одержимого интернетом, на которое меня оскорбляют, даже если предположить, что мое имя может быть лучше.

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

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

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

Ответ 16

try
{
  ...
  goto finally;
}
catch(...)
{
  ...
  goto finally;
}
finally:
{
  ...
}