Существует ли стандартный класс С++ для установки переменной в значение при выходе из области действия

В рамках функции-члена я хочу временно установить переменную-член в определенное значение.

Затем, когда эта функция вернется, я хочу, чтобы reset эта переменная-член указала на известное значение.

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

void MyClass::MyMemberFunction() {
    struct SetBackToFalse {
        SetBackToFalse(bool* p): m_p(p) {}
        ~SetBackToFalse() {*m_p=false;}
    private:
        bool* m_p;
    };

    m_theVariableToChange = true;
    SetBackToFalse resetFalse( &m_theVariableToChange ); // Will reset the variable to false.

    // Function body that may throw.
}

Кажется, это так очевидно, что мне было интересно, если бы такой класс шаблонов выполнял это в стандартной библиотеке С++?

Ответ 1

Пока нет (для этого были предложения). Но реализация родового достаточно проста:

struct scope_exit {
  std::function<void()> f_;
  explicit scope_exit(std::function<void()> f) noexcept : f_(std::move(f)) {}
  ~scope_exit() { if (f_) f_(); }
};
// ...
m_theVariableToChange = true;
scope_exit resetFalse([&m_theVariableToChange]() { m_theVariableToChange = false; });

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

Маркировка их как = delete сделает приведенное выше минимальным решением. В дальнейшем; при желании можно разрешить перемещение, но копирование должно быть запрещено.


Более полная scope_exit будет выглядеть (онлайн-демонстрация здесь);

template <typename F>
struct scope_exit {
  F f_;
  bool run_;
  explicit scope_exit(F f) noexcept : f_(std::move(f)), run_(true) {}
  scope_exit(scope_exit&& rhs) noexcept : f_((rhs.run_ = false, std::move(rhs.f_))), run_(true) {}
  ~scope_exit()
  {
    if (run_)
      f_(); // RAII semantics apply, expected not to throw
  }

  // "in place" construction expected, no default ctor provided either
  // also unclear what should be done with the old functor, should it
  // be called since it is no longer needed, or not since *this is not
  // going out of scope just yet...
  scope_exit& operator=(scope_exit&& rhs) = delete;
  // to be explicit...
  scope_exit(scope_exit const&) = delete;
  scope_exit& operator=(scope_exit const&) = delete;
};

template <typename F>
scope_exit<F> make_scope_exit(F&& f) noexcept
{
  return scope_exit<F>{ std::forward<F>(f) };
}

Примечания по реализации;

  • std::function<void()> может использоваться для стирания типа функтора. std::function<void()> предлагает гарантии исключения для конструкторов перемещения на основе специфики исключения для удерживаемой функции. Образец этой реализации найден здесь
  • Эти спецификации исключений согласованы с предложением С++ и реализацией GSL.
  • Я отредактировал большую часть мотивации для noexcept, более существенные детали найдены в предложение С++
  • "Обычная" RAII-семантика деструктора, следовательно, применима функция "выхода области"; он не будет throw, это также соответствует спецификации С++ 11 спецификации исключения по умолчанию для деструктора. См. cppreference, SO Q & A, GotW # 47 и HIС++

Можно найти другие реализации:

Ответ 2

Вы можете "злоупотреблять" shared_ptr для этого:

m_theVariableToChange = true;
std::shared_ptr<void> resetFalse(nullptr, [&](void*){ m_theVariableToChange = false; });

Если есть проблемы с использованием void в качестве параметра шаблона T, я нашел следующее в стандарте С++:

20.8.2.2§2:

... Параметр шаблона T shared_ptr может быть неполным.

Это означает, что T используется только как указатель, поэтому использование void должно быть в порядке.

Ответ 3

Нет стандартной версии этого.

CppGoreGuidelines Библиотека поддержки (GSL) имеет обобщенную версию этого наконец, но эта библиотека еще не является качеством производства. Его определенно рекомендуемая практика.

E.19: использовать объект final_action для выражения очистки, если подходящий дескриптор ресурса не доступен

Причина

finally является менее подробным и труднее ошибиться, чем try/catch.

Пример

void f(int n)
{
    void* p = malloc(1, n);
    auto _ = finally([p] { free(p); });
    // ...
}

Примечание

finally не такой грязный, как try/catch, но он по-прежнему является ad-hoc. Предпочитают правильные объекты управления ресурсами.

Ответ 4

Аналогичный вопрос: Самый простой и удобный С++ 11 ScopeGuard

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

Например, решение из этого ответа для вашего кода будет:

scope_guard guard1 = [&]{ m_theVariableToChange = false; };

Другой ответ на этот вопрос отмечает, что аналогичная концепция была предложена для стандартизации С++ 17; и есть также представленное решение С++ 03.