Чи підтримує C ++ " остаточно " блоки?
Що таке ідіома RAII ?
Яка різниця між ідіомою RAII C ++ та оператором C # 's' using ?
Чи підтримує C ++ " остаточно " блоки?
Що таке ідіома RAII ?
Яка різниця між ідіомою RAII C ++ та оператором C # 's' using ?
Відповіді:
Ні, C ++ не підтримує блоки "нарешті". Причина полягає в тому, що натомість C ++ підтримує RAII: "Придбання ресурсів - це ініціалізація" - неправильне ім'я † для дійсно корисної концепції.
Ідея полягає в тому, що деструктор об'єкта відповідає за звільнення ресурсів. Коли об'єкт має автоматичну тривалість зберігання, деструктор об'єкта буде викликаний тоді, коли блок, в якому він був створений, виходить - навіть коли цей блок вийшов за наявності винятку. Ось пояснення теми Б'ярне Струструпа .
Поширене використання для 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 для всіх членів класу, що керують ресурсами, ви можете піти з використання дуже простого, можливо, навіть деструктора для класу власника, можливо, навіть за замовчуванням, оскільки йому не потрібно вручну керувати ресурсами свого члена-члена . (Дякую Майку Б, що вказав на це.)
Для тих, хто знайомий з C # або VB.NET, ви можете визнати, що RAII подібний до детермінованого знищення .NET, використовуючи оператори IDisposable та "using" . Дійсно, два методи дуже схожі. Основна відмінність полягає в тому, що RAII детерміновано звільнить будь-який тип ресурсів - включаючи пам'ять. При впровадженні IDisposable в .NET (навіть мова .NET C ++ / CLI) ресурси будуть детерміновано вивільнені, за винятком пам'яті. У .NET пам'ять не вивільняється детерміновано; пам'ять вивільняється лише під час циклів вивезення сміття.
† Деякі люди вважають, що "Знищення - це надбавка до ресурсів" - більш точна назва ідіоми RAII.
У C ++ нарешті НЕ потрібен через RAII.
RAII переносить відповідальність за безпеку винятків з користувача об'єкта на проектувальника (та реалізатора) об'єкта. Я зауважу, що це правильне місце, оскільки тоді вам потрібно лише один раз (при розробці / реалізації) отримати правильність безпеки виключень. Використовуючи нарешті, вам потрібно правити безпеку винятків під час кожного використання об'єкта.
Також код IMO виглядає акуратніше (див. Нижче).
Приклад:
Об'єкт бази даних. Щоб переконатися, що використовується з'єднання БД, його потрібно відкривати та закривати. За допомогою 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 робить використання об'єкта БД правильно дуже просто. Об'єкт БД буде правильно закриватися за допомогою деструктора незалежно від того, як ми намагатимемося і зловживати ним.
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.
}
}
}
При остаточному використанні правильне використання об'єкта делегується користувачеві об'єкта. тобто користувач об'єкта несе відповідальність за те, щоб чітко закрити з'єднання БД. Тепер ви можете стверджувати, що це можна зробити у фіналізаторі, але ресурси можуть мати обмежену доступність або інші обмеження, і, таким чином, ви, як правило, хочете контролювати викид об'єкта, а не покладатися на недетерміновану поведінку сміттєзбірника.
Також це простий приклад.
Якщо у вас є кілька ресурсів, які потрібно випустити, код може ускладнитися.
Більш детальний аналіз можна знайти тут: http://accu.org/index.php/journals/236
// Make sure not to throw exception if one is already propagating.
Для деструкторів С ++ важливо саме з цієї причини не кидати винятки.
RAII, як правило, краще, але ви можете легко мати остаточну семантику в C ++. Використання невеликої кількості коду.
Крім того, основні рекомендації C ++ дають нарешті.
Ось посилання на реалізацію GSL Microsoft та посилання на реалізацію Мартіна Моена
Bjarne Stroustrup кілька разів говорив, що все, що є в GSL, це означало, що в кінцевому підсумку йде стандарт. Отже, це має бути надійним способом використання остаточно .
Ви можете легко реалізувати себе, якщо хочете, продовжуйте читати.
У C ++ 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 у програмі C ++.
Мати справжній клас, який керує ресурсами і таким чином уникає будь-яких витоків, як правило, краще, але це, нарешті , корисно у випадках, коли створення класу звучить як надмір.
Крім того, мені подобається це краще, ніж інші мови, нарешті, тому що, якщо ви використовуєте природний спосіб, ви пишете код закриття поблизу коду відкриття (у моєму прикладі новий і видалення ), а знищення слідує за побудовою в порядку LIFO, як зазвичай в C ++. Єдиним недоліком є те, що ви отримуєте автоматичну змінну, яку ви насправді не використовуєте, і синтаксис лямбда робить її трохи галасливою (у моєму прикладі в четвертому рядку значення мають лише нарешті слово та {} -блок праворуч, відпочинок - це по суті шум).
Ще один приклад:
[...]
auto precision = std::cout.precision();
auto set_precision_back = finally( [precision, &std::cout]() { std::cout << std::setprecision(precision); } );
std::cout << std::setprecision(3);
Відключити елемент є корисним , якщо , нарешті , повинен бути викликаний тільки в разі виходу з ладу. Наприклад, вам потрібно скопіювати об’єкт у три різні контейнери, ви можете налаштувати остаточно скасувати кожну копію та відключити після того, як всі копії будуть успішні. Зробивши це, якщо руйнування не вдасться кинути, ви гарантуєте сувору гарантію.
приклад відключення :
//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(); }
Якщо ви не можете використовувати C ++ 11, ви все одно можете нарешті , але код стає трохи більш звичним. Просто визначте структуру лише з конструктором і деструктором, конструктор бере посилання на все необхідне, і деструктор виконує необхідні вам дії. Це в основному те, що робить лямбда, зроблене вручну.
#include <iostream>
int main() {
int* a = new int;
struct Delete_a_t {
Delete_a_t(int* p) : p_(p) {}
~Delete_a_t() { delete p_; std::cout << "leaving the block, deleting a!\n"; }
int* p_;
} delete_a(a);
std::cout << "doing something ...\n"; }
FinalAction
в основному те саме, що і народна ScopeGuard
ідіома, лише з іншою назвою.
Крім спрощення очищення об'єктів на основі стека, RAII також корисний, оскільки те ж «автоматичне» очищення відбувається, коли об’єкт є членом іншого класу. Коли клас власності знищений, ресурс, керований класом RAII, очищається, оскільки в результаті цього викликається dtor цього класу.
Це означає, що коли ви досягаєте RAII nirvana і всі члени класу використовують RAII (як розумні покажчики), ви можете піти з дуже простим (можливо, навіть за замовчуванням) dtor класом власника, оскільки йому не потрібно вручну керувати своїм життя ресурсів учасників.
Чому так, що навіть керовані мови забезпечують остаточний блок, незважаючи на те, що ресурси, відповідно, автоматично перебираються сміттєзбірником?
Власне, мовам, заснованим на збирачах сміття, потрібно "нарешті" більше. Збирач сміття не знищує ваші предмети своєчасно, тому на нього не можна покластись, щоб правильно очистити проблеми, що не стосуються пам’яті.
Що стосується динамічно розподілених даних, багато хто стверджує, що вам слід використовувати смарт-покажчики.
Однак ...
RAII переносить відповідальність за безпеку винятків з користувача об'єкта на проектувальника
На жаль, це власне падіння. Старі звички програмування на C важко вмирають. Якщо ви використовуєте бібліотеку, написану на мові C, або в дуже стилі C, RAII не використовувався. Окрім того, щоб переписати весь інтерфейс API, саме з цим потрібно працювати. Тоді відсутність "нарешті" дійсно прикушує.
CleanupFailedException
. Чи є якийсь правдоподібний спосіб досягти такого результату за допомогою RAII?
SomeObject.DoSomething()
метод і хоче знати, чи вдалося (1), (2) не вдалося побічних ефектів , (3) не вдалося зі побічними ефектами, з якими абонент готовий впоратися. або (4) не вдалося отримати побічні ефекти, з якими абонент не може впоратися. Тільки той, хто телефонує, дізнається, в яких ситуаціях він може впоратися і з чим не вдається; що потребує абонента - це спосіб дізнатися, яка ситуація. Це дуже погано, що немає стандартного механізму надання найважливішої інформації про виняток.
Ще одна "нарешті" блокова емуляція з використанням C ++ 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 :(
Тепер блок "нарешті" доступний на C ++ 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
Особисто мені не подобається "макрос" версія ідіоми "нарешті", і я вважаю за краще використовувати чисту функцію "with_finally", хоча синтаксис у цьому випадку є більш об'ємним.
Ви можете перевірити код вище: http://coliru.stacked-crooked.com/a/1d88f64cb27b3813
PS
Якщо вам потрібен остаточний блок у вашому коді, то масштабні охоронці або макроси 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(); });
...
Вибачте за викопання такої старої теми, але в наступних міркуваннях є значна помилка:
RAII переносить відповідальність за безпеку винятків з користувача об'єкта на проектувальника (та реалізатора) об'єкта. Я зауважу, що це правильне місце, оскільки тоді вам потрібно лише один раз (при розробці / реалізації) отримати правильність безпеки виключень. Використовуючи нарешті, вам потрібно правити безпеку винятків під час кожного використання об'єкта.
Частіше за все вам доводиться мати справу з динамічно виділеними об'єктами, динамічними номерами об'єктів і т. Д. У межах блоку спробу деякий код може створити багато об'єктів (скільки визначається під час виконання) та зберігати покажчики на них у списку. Зараз це не екзотичний сценарій, але дуже поширений. У цьому випадку ви хочете писати такі речі
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();
}
}
Крім того: чому так, що навіть керовані місця проживання забезпечують остаточний блок, незважаючи на те, що ресурси сміттєзбірника автоматично розподіляються автоматично?
Підказка: з "нарешті" ви можете зробити більше, ніж просто розміщення пам'яті.
new
не повертає NULL, він замість цього викидає виняток
std::shared_ptr
та std::unique_ptr
безпосередньо в stdlib.
FWIW, Microsoft Visual C ++ все ж підтримує спробу, нарешті, вона історично використовувалася в програмах 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, однак, порушили цей механізм.
Як зазначено в інших відповідях, C ++ може підтримувати finally
подібну функціональність. Реалізація цієї функціональності, яка, мабуть, найбільш близька до того, що вона є частиною стандартної мови, - це супровід основних інструкцій C ++ , набір найкращих практик використання C ++ за редакцією Bjarne Stoustrup та Herb Sutter. Реалізаціяfinally
є частиною бібліотеки Guidelines підтримки (GSL). У Посібниках використання finally
рекомендується використовувати при роботі з інтерфейсами старого стилю, а також має власне керівництво під назвою Використовувати кінцевий об'єкт запуску для вираження очищення, якщо не існує відповідної обробки ресурсів .
Отже, не тільки підтримується 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 дуже схожі на відповіді Паоло.Болзоні . Одна відмінність полягає в тому, що об'єкту, створеному в результаті, gsl::finally()
не вистачає disable()
виклику. Якщо вам потрібна ця функціональність (скажімо, для повернення ресурсу після його збирання і жодних винятків не буде), можливо, ви віддасте перевагу реалізації Paolo. В іншому випадку використання GSL так само близьке до використання стандартизованих функцій, як ви отримаєте.
Не дуже, але ви можете імітувати їх до деякого розширення, наприклад:
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;
Зауважте, що остаточний блок може сам викинути виняток перед повторним скиданням вихідного винятку, тим самим відкидаючи вихідний виняток. Це точно така ж поведінка, як і в остаточному блоці Java. Крім того, ви не можете використовувати return
всередині блоків try & catch.
std::exception_ptr e; try { /*try block*/ } catch (...) { e = std::current_exception(); } /*finally block*/ if (e) std::rethrow_exception(e);
finally
блоку.
Я придумав finally
макрос, який можна використовувати майже як ¹ finally
ключове слово на Java; він використовує std::exception_ptr
і друзів, лямбда-функції і std::promise
, тому це вимагає C++11
або вище; він також використовує складений вираз експресії GCC розширення, яке також підтримується кланг.
УВАГА : більш рання версія цієї відповіді використовувала іншу реалізацію концепції з багатьма обмеженнями більше.
Спочатку давайте визначимо клас помічника.
#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()
's, оскільки вони живуть у межах лямбда-функції;catch()
блок після try
: це вимога C ++;try
та catch()'s
блоках, компіляція не вдасться, оскільки finally
макрос розшириться до коду, який потрібно повернути void
. Це може бути, еее, анулюється ред при наявності finally_noreturn
макрокоманди сортів.Загалом, я не знаю, чи коли-небудь я сам використовував би цей матеріал, але грати з ним було весело. :)
catch(xxx) {}
блок на початку finally
макросу, де xxx - це фальшивий тип лише для того, щоб мати хоча б один блок спіймання.
catch(...)
, чи не так?
xxx
у приватному просторі імен, який ніколи не буде використовуватися.
У мене є випадок використання, коли я думаю, що він 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);
}
}
Я думаю, що це більш логічно, якщо ставити остаточну заяву на початок циклу, оскільки це відбувається після виходу циклу ... але це бажано думати, оскільки ми не можемо це зробити в 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);
}
}
Як багато хто заявляв, рішення полягає у використанні функцій C ++ 11, щоб уникнути блокування остаточно. Однією з особливостей є 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);
}
Ще кілька знайомств із використанням унікального_ptr з контейнерами стандартної бібліотеки C ++ є тут
Я хотів би запропонувати альтернативу.
Якщо ви хочете, щоб нарешті блок називався завжди, просто поставте його після останнього блоку лову (який, мабуть, має бути 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();
Якщо ви хочете, нарешті, блокувати як останнє, що потрібно зробити, коли викинуто будь-який виняток, ви можете використовувати булеву локальну змінну - перед запуском ви встановите її на помилку та поставите справжнє призначення в самому кінці блоку спробу, а потім після блоку захоплення перевіряйте змінну значення:
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();
}
Я також вважаю, що RIIA не є повністю корисною заміною для обробки винятків і, нарешті,. До речі, я також думаю, що RIIA - це погана назва навколо. Я називаю ці типи класів «двірниками» і використовую їх ЛОТ. У 95% часу вони не ініціалізують і не набувають ресурсів, вони застосовують певні зміни на основі масштабів, або беруть щось вже створене і переконуються, що воно знищене. Це офіційне найменування одержимого Інтернету, я зловживаюсь тим, що навіть припускаю, що моє ім’я може бути кращим.
Я просто не думаю, що доцільно вимагати, щоб кожне складне налаштування певного спеціального списку речей містило клас, записаний для його вмісту, щоб уникнути ускладнень при очищенні все резервного копіювання, перед тим, як потребувати лову кількох типи винятків, якщо щось піде не так у процесі. Це призведе до безлічі спеціальних занять, які в іншому випадку просто не знадобляться.
Так, це добре для класів, призначених для управління певним ресурсом, або загальних, які призначені для обробки набору подібних ресурсів. Але навіть якщо у всіх речах є такі обгортки, координація очищення може бути не просто простим викликом деструкторів у зворотному порядку.
Я думаю, що для C ++ є доречним сенс мати нарешті. Я маю на увазі, джеже, стільки шматочків і бобів було наклеєно на нього протягом останніх десятиліть, що, здається, дивні люди раптом стануть консервативними над чимось на кшталт, що, нарешті, може бути дуже корисним і, мабуть, нічим не таким складним, як деякі інші речі, які були додано (хоча це лише здогадка з мого боку.)
try
{
...
goto finally;
}
catch(...)
{
...
goto finally;
}
finally:
{
...
}
finally
не робить.