Поддерживает ли C ++ блоки finally? (А что это за «RAII», о котором я продолжаю слышать?) - PullRequest
249 голосов
/ 02 октября 2008

Поддерживает ли C ++ блоки ' finally '?

Что такое RAII идиома ?

В чем разница между C ++ идиомой RAII и C # 'using' оператором ?

Ответы [ 16 ]

3 голосов
/ 30 мая 2014

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

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. Кроме того, вы не можете использовать return внутри блоков try & catch.

2 голосов
/ 01 декабря 2015

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

Если бы C ++ поддерживал его, вы бы хотели, чтобы ваш код выглядел так:

    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 в начале цикла, поскольку это происходит после выхода из цикла ... но это заблуждение, потому что мы не можем сделать это в C ++. Обратите внимание, что очередь downstream подключена к другому потоку, поэтому вы не можете поместить дозорный push(nullptr) в деструктор downstream, поскольку он не может быть уничтожен в данный момент ... он должен остаться в живых пока другой поток не получит nullptr.

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

    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);
        }
    }
1 голос
/ 02 сентября 2015

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

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

Если вы хотите, чтобы блок finally был последним, что было сделано при возникновении какого-либо исключения, вы можете использовать логическую локальную переменную - перед запуском установите значение false и присвойте значение true в самом конце блока 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();
}
1 голос
/ 10 июля 2014

Как утверждают многие, решение состоит в том, чтобы использовать функции C ++ 11, чтобы избежать блоков finally. Одна из особенностей unique_ptr.

Вот ответ Мефана, написанный с использованием шаблонов 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 с контейнерами стандартной библиотеки C ++: здесь

0 голосов
/ 18 декабря 2018

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

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

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

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

0 голосов
/ 23 апреля 2010
try
{
  ...
  goto finally;
}
catch(...)
{
  ...
  goto finally;
}
finally:
{
  ...
}
...