Действительно ли использование ScopeGuard приводит к улучшению кода?

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

C++ 0x / C++ 11 теперь делает это с помощью shared_ptr.

Lilian A. Moraru 16.03.2013 02:44

Я действительно вижу, что это дает вам больше силы. Пример с базой данных довольно хорош. Используя только shared_ptr, он вызовет деструктор, который обычно закрывает соединение только при использовании ScopedGuard, вы действительно можете выполнить откат в случае исключения ...

Lilian A. Moraru 16.03.2013 02:51
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
32
2
11 793
8
Перейти к ответу Данный вопрос помечен как решенный

Ответы 8

Ответ принят как подходящий

Это определенно улучшает ваш код. Ваше предварительно сформулированное утверждение о том, что это неясно и что код заслуживает использования блока catch, просто неверно в C++, потому что RAII - это устоявшаяся идиома. Обработка ресурсов в C++ является выполняется путем получения ресурсов, а сборка мусора выполняется неявными вызовами деструктора.

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

RAII (включая ScopeGuard) не является непонятной техникой в ​​C++, но является твердо установленной передовой практикой.

+1 за приверженность современным методам C++. Также следует отметить, что Александреску представил здесь новую версию ScopeGuard: channel9.msdn.com/Shows/Going+Deep/…, которая намного проще в использовании. Micht стоит отредактировать.

odinthenerd 18.02.2013 03:34

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

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

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 для

  • установите GLcolor или GLwidth обратно в исходное состояние, как только я нарисовал фигуру
  • убедитесь, что у файла есть fclosed, как только я его обработал fopen.
  • сбросить указатель мыши в исходное состояние после того, как я изменил его на gears / hourgrlass во время выполнения медленной функции
  • сбросить состояние sorting QListView обратно в его предыдущее состояние, как только я временно закончил изменять его QListViewItems - я не хотел, чтобы список менялся каждый раз, когда я изменял текст одного элемента ...

используя простой класс RAII

Вот как мой код выглядел с моими вручную созданными классами 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 требует

    • 3 строки, чтобы сохранить предыдущее значение (состояние) и установить его на новое, и
    • 2 переменных стека (previous_width и guard, опять же) для сохранения предыдущего состояния
  • RAII class ручной работы требует

    • 1 читаемая строка для установки нового состояния и сохранения предыдущего, и
    • 1 переменная стека (guard) для хранения предыдущего состояния.

Вывод

Я думаю, что такие примеры, как

void some_function() {
    sg::scoped_guard([](){ cout << "this is printed last"; }

    cout << "this is printed first";
}

не являются доказательством полезности scoped_guard.

Я надеюсь, что кто-нибудь сможет показать мне, почему я не получаю ожидаемого прироста от scoped_guard.

Я убежден, что RAII можно лучше использовать, написав короткие вручную созданные классы, чем используя более общий, но сложный в использовании scoped_guard.

Другие вопросы по теме