Я наткнулся на Эта статья, написанный Андреем Александреску и Петру Маргиняном много лет назад, который представляет и обсуждает служебный класс ScopeGuard для написания кода, безопасного для исключений. Я хотел бы знать, действительно ли кодирование с этими объектами приводит к лучшему коду или оно запутывает обработку ошибок, в том смысле, что, возможно, обратный вызов охранника будет лучше представлен в блоке catch? Есть ли у кого-нибудь опыт использования их в реальном производственном коде?
Я действительно вижу, что это дает вам больше силы. Пример с базой данных довольно хорош. Используя только shared_ptr, он вызовет деструктор, который обычно закрывает соединение только при использовании ScopedGuard, вы действительно можете выполнить откат в случае исключения ...





Это определенно улучшает ваш код. Ваше предварительно сформулированное утверждение о том, что это неясно и что код заслуживает использования блока catch, просто неверно в C++, потому что RAII - это устоявшаяся идиома. Обработка ресурсов в C++ является выполняется путем получения ресурсов, а сборка мусора выполняется неявными вызовами деструктора.
С другой стороны, явные блоки catch раздувают код и вносят небольшие ошибки, потому что поток кода становится намного более сложным, а обработка ресурсов должна выполняться явно.
RAII (включая ScopeGuard) не является непонятной техникой в C++, но является твердо установленной передовой практикой.
+1 за приверженность современным методам C++. Также следует отметить, что Александреску представил здесь новую версию ScopeGuard: channel9.msdn.com/Shows/Going+Deep/…, которая намного проще в использовании. Micht стоит отредактировать.
Я не использовал этот конкретный шаблон, но раньше использовал нечто подобное. Да, это действительно приводит к более ясному коду по сравнению с одинаково надежным кодом, реализованным по-разному.
Я часто использую его для защиты использования памяти, вещей, которые нужно освободить, которые были возвращены из ОС. Например:
DATA_BLOB blobIn, blobOut;
blobIn.pbData=const_cast<BYTE*>(data);
blobIn.cbData=length;
CryptUnprotectData(&blobIn, NULL, NULL, NULL, NULL, CRYPTPROTECT_UI_FORBIDDEN, &blobOut);
Guard guardBlob=guardFn(::LocalFree, blobOut.pbData);
// do stuff with blobOut.pbData
Да.
Если есть один фрагмент кода C++, который я мог бы рекомендовать каждому программисту C++ потратить 10 минут на изучение, это ScopeGuard (теперь часть свободно доступного Библиотека Локи).
Я решил попробовать использовать (слегка измененную) версию ScopeGuard для небольшой графической программы Win32, над которой я работал. Win32, как вы, возможно, знаете, имеет много разных типов ресурсов, которые необходимо закрывать разными способами (например, дескрипторы ядра обычно закрываются с помощью CloseHandle(), GDI BeginPaint() должен быть связан с EndPaint() и т. д.). Я использовал ScopeGuard со всеми этими ресурсами, и также для выделения рабочих буферов с new (например, для преобразования набора символов в / из Unicode).
Что меня поразило, так это количество короче в программе. По сути, это беспроигрышный вариант: ваш код становится короче и надежнее одновременно. Будущие изменения кода не могу ничего протечь. Они просто не могут. Как это круто?
Я должен сказать, нет, это не так. Ответы здесь помогают продемонстрировать, почему это действительно ужасная идея. Обработка ресурсов должна выполняться с помощью повторно используемых классов. Единственное, чего они достигли, используя защиту области видимости, - это нарушить DRY до wazoo и дублировать свой код освобождения ресурсов по всей своей кодовой базе, вместо того, чтобы писать один класс для обработки ресурса, а затем все для всей партии.
Стражи области видимости Если имеют какое-либо реальное применение, обработка ресурсов - одна из них нет. В этом случае они значительно уступают обычному RAII, поскольку RAII дедуплицируется и выполняется автоматически, а защита осциллографа - это ручное дублирование кода или отказ от него.
Я думаю, что в приведенных выше ответах отсутствует одно важное замечание. Как указывали другие, вы можете использовать ScopeGuard для освобождения выделенных ресурсов независимо от сбоя (исключения). Но это может быть не единственное, для чего вы можете захотеть использовать Scope Guard. Фактически, примеры в связанной статье используют ScopeGuard для другой цели: трансляции. Короче говоря, это может быть полезно, если у вас есть несколько объектов (даже если эти объекты правильно используют RAII), которые вам нужно поддерживать в состоянии, которое так или иначе коррелирует. Если изменение состояния любого из этих объектов приводит к возникновению исключения (что, как я полагаю, обычно означает, что его состояние не изменилось), то все уже примененные изменения необходимо откатить. Это создает собственный набор проблем (что, если откат тоже не удастся?). Вы можете попробовать развернуть свой собственный класс, который управляет такими коррелированными объектами, но по мере того, как их количество увеличивается, это может стать беспорядочным, и вы, вероятно, в любом случае вернетесь к использованию ScopeGuard для внутренних целей.
Да.
Это было настолько важно в C++, что даже специальный синтаксис для него в D:
void somefunction() {
writeln("function enter");
// c++ has similar constructs but not in syntax level
scope(exit) writeln("function exit");
// do what ever you do, you never miss the function exit output
}
Мой опыт показывает, что использование scoped_guard намного уступает любому из коротких многоразовых классов RAII, которые вы можете написать вручную.
Перед тем, как попробовать scoped_guard, я написал классы RAII для
fclosed, как только я его обработал fopen.sorting QListView обратно в его предыдущее состояние, как только я временно закончил изменять его QListViewItems - я не хотел, чтобы список менялся каждый раз, когда я изменял текст одного элемента ...Вот как мой код выглядел с моими вручную созданными классами RAII:
class scoped_width {
int m_old_width;
public:
scoped_width(int w) {
m_old_width = getGLwidth();
setGLwidth(w);
}
~scoped_width() {
setGLwidth(m_old_width);
}
};
void DrawTriangle(Tria *t)
{
// GLwidth=1 here
auto guard = scoped_width(2); // sets GLwidth=2
draw_line(t->a, t->b);
draw_line(t->b, t->c);
draw_line(t->c, t->a);
setGLwidth(5);
draw_point(t->a);
draw_point(t->b);
draw_point(t->c);
} // scoped_width sets GLwidth back to 1 here
Очень простая реализация для scoped_width и достаточно многоразовая.
Очень простой и понятный для потребителя.
scoped_guard (C++ 14)Теперь, с помощью scoped_guard, я должен захватить существующее значение в представителе ([]), чтобы передать его в обратный вызов охранника:
void DrawTriangle(Tria *t)
{
// GLwidth=1 here
auto guard = sg::make_scoped_guard([w=getGLwidth()](){ setGLwidth(w); }); // capture current GLwidth in order to set it back
setGLwidth(2); // sets GLwidth=2
draw_line(t->a, t->b);
draw_line(t->b, t->c);
draw_line(t->c, t->a);
setGLwidth(5);
draw_point(t->a);
draw_point(t->b);
draw_point(t->c);
} // scoped_guard sets GLwidth back to 1 here
Вышеупомянутое даже не работает на C++ 11. Не говоря уже о том, что попытка представить состояние лямбды таким образом режет мне глаза.
scoped_guard (C++ 11)В C++ 11 вам нужно сделать это:
void DrawTriangle(Tria *t)
{
// GLwidth=1 here
int previous_width = getGLwidth(); // explicitly capture current width
auto guard = sg::make_scoped_guard([=](){ setGLwidth(previous_width); }); // pass it to lambda in order to set it back
setGLwidth(2); // sets GLwidth=2
draw_line(t->a, t->b);
draw_line(t->b, t->c);
draw_line(t->c, t->a);
setGLwidth(5);
draw_point(t->a);
draw_point(t->b);
draw_point(t->c);
} // scoped_guard sets GLwidth back to 1 here
Как вы видете,
сноппер scoped_guard требует
previous_width и guard, опять же) для сохранения предыдущего состоянияRAII class ручной работы требует
guard) для хранения предыдущего состояния.Я думаю, что такие примеры, как
void some_function() {
sg::scoped_guard([](){ cout << "this is printed last"; }
cout << "this is printed first";
}
не являются доказательством полезности scoped_guard.
Я надеюсь, что кто-нибудь сможет показать мне, почему я не получаю ожидаемого прироста от scoped_guard.
Я убежден, что RAII можно лучше использовать, написав короткие вручную созданные классы, чем используя более общий, но сложный в использовании scoped_guard.
C++ 0x / C++ 11 теперь делает это с помощью shared_ptr.