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

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

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

Чтобы защититься от исключений и множественных возвратов, я сделал это с помощью простого класса, подобного 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.
}

Это кажется настолько банальным, что мне стало интересно, есть ли в стандартной библиотеке C++ такой шаблонный класс?


person Didier Trosset    schedule 15.04.2016    source источник
comment
Интересная идея. Что-то мне интересно: есть ли причина использовать здесь указатель вместо ссылки?   -  person underscore_d    schedule 15.04.2016
comment
Думаю, в стандартной библиотеке такого нет. Андрей Александреску однажды построил некоторое обобщение вышеизложенного с помощью длинных макро-хаков.   -  person Baum mit Augen    schedule 15.04.2016
comment
@underscore_d Нет. Вы можете реализовать это с помощью ссылки.   -  person Didier Trosset    schedule 15.04.2016
comment
Также есть Boost.ScopeExit .   -  person Baum mit Augen    schedule 15.04.2016
comment
Это кажется настолько банальным. Более банальным кажется не делать что-то настолько запутанное и странное. Почему бы вам не использовать вместо этого другую переменную?   -  person Lightness Races in Orbit    schedule 15.04.2016
comment
@BaummitAugen: концепция ScopeGuard Действительно придумал Петру Маргинян, а не Андрею Александресу. Не удалось найти авторитетную ссылку, поэтому мне придется взять слово Херба Саттера для этого.   -  person IInspectable    schedule 15.04.2016
comment
@IInspectable Я не знаю, кто это придумал, это единственная известная мне реализация. Но спасибо за ссылки. Кроме того, из стандартных предложений : Это предложение включает в себя то, что Андрей Александреску давно назвал защитой области действия и снова объяснил на C++ Now 2012 (). Iirc это доклад, о котором я говорил выше.   -  person Baum mit Augen    schedule 15.04.2016
comment
Эта конструкция будет абсолютно смертельной, если доступ к объекту осуществляется из нескольких потоков.   -  person Pete Becker    schedule 15.04.2016
comment
@PeteBecker Очень верно. Спасибо, что заметили это.   -  person Didier Trosset    schedule 15.04.2016
comment
@PeteBecker Столь же смертоносен, как var = false; ...   -  person M.M    schedule 18.04.2016
comment
@MM - не совсем, так как код, который сбрасывает переменную, будет более или менее невидимым.   -  person Pete Becker    schedule 18.04.2016
comment
Питер Соммерлад предложил ввести в стандарт такую ​​функцию scope_guard: open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3949.pdf   -  person Didier Trosset    schedule 23.04.2016


Ответы (4)


Пока нет (были предложения по этому поводу). Но реализовать общий достаточно просто;

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()> предлагает гарантии исключений для конструкторов перемещения на основе исключений, специфичных для удерживаемой функции. Пример этой реализации находится здесь
  • Эти спецификации исключений согласуются с предложением C++ и реализациями GSL.
  • Я отредактировал большую часть мотивации для noexcept, более существенные подробности можно найти в Предложение C++
  • «Обычная» семантика RAII деструктора, следовательно, применима функция «выход из области действия»; это не будет throw, это также согласуется со спецификацией C++11 по спецификации исключения по умолчанию для деструктора. См. cppreference, SO Q&A, GotW#47 и HIC++

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

person Niall    schedule 15.04.2016
comment
Я добавлю дополнительные примечания и спецификации исключений, как только у меня будет возможность. - person Niall; 16.04.2016
comment
(Документ Питера Соммерлада, на который вы ссылаетесь, имеет новую редакцию: P0052r1.) - person Kerrek SB; 16.04.2016
comment
@КеррекСБ. Я рад, что это предложение не отпало в сторону. - person Niall; 18.04.2016

Вы можете «оскорбить» shared_ptr за это:

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

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

20.8.2.2§2:

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

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

person alain    schedule 15.04.2016
comment
Я не понизил голос, но, вероятно, не гарантируется, что средство удаления будет выполнено, когда первый аргумент конструктора равен нулю - person Viktor Sehr; 15.04.2016
comment
Да, это может быть причиной, спасибо @Viktor. Тем не менее, я проверил это, это, кажется, работает. Другой причиной может быть использование void, я думаю. Я не уверен, что это законно. - person alain; 15.04.2016
comment
Уважаемый downvoter, если причина была std::nullptr, то сейчас исправлено. Глупая ошибка, извините за это. - person alain; 15.04.2016
comment
Спасибо, теперь я узнал новую причину, почему using namespace std это плохо ;-) - person alain; 15.04.2016
comment
@ВикторСер. Учитывая ~shared_ptr() эффекты Если *this пуст или разделяет владение с другим экземпляром shared_ptr (use_count() › 1), побочные эффекты отсутствуют. и Объект shared_ptr является пустым, если он не владеет указателем. §20.10.2.2, это должно быть нормально для его построения с нулевым указателем - тогда он владеет нулевым указателем; но я думаю, что это просто нормально, я уверен, что изначально это было предназначено для работы таким образом (но, похоже, это работает). - person Niall; 15.04.2016
comment
@ален. Это работает, но нужно использовать осторожно. Семантика shared_ptr допускает совместное владение, и если shared_ptr непреднамеренно скопирован, выполнение при выходе из области действия может работать не так, как ожидалось. Ответственность за обеспечение отсутствия копий лежит на разработчике - компилятор не сможет помочь в этом отношении. Как в сторону; unique_ptr здесь не сработает, поскольку его деструктору требуется ненулевой указатель, прежде чем он запустит средство удаления. - person Niall; 15.04.2016
comment
Спасибо за информацию @Niall. Однако копия в другой shared_ptr с той же областью действия будет в порядке. (Думаю, это самый распространенный случай непреднамеренного копирования.) - person alain; 15.04.2016
comment
@M.M. Я не слежу. Ответ инициализирует shared_ptr с помощью nullptr, а не адреса (не указателя) какой-либо переменной. - person Niall; 18.04.2016
comment
@Niall Хорошо, я представлял, что unique_ptr будет указывать на рассматриваемую переменную, а пользовательское средство удаления установит для нее значение false вместо вызова delete. Но это хромая идея, поскольку она неинтуитивна для читателя. - person M.M; 18.04.2016
comment
@Niall, это работает не случайно, это функция shared_ptr. Соответствующий конструктор очень четко говорит, что он владеет указателем. - person Jonathan Wakely; 18.04.2016

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

Библиотека поддержки 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, но он все еще случайный. Предпочитайте правильные объекты управления ресурсами.

person Galik    schedule 15.04.2016

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

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

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

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

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

person M.M    schedule 18.04.2016