Общий способ сбросить переменную-член до исходного значения с помощью стека?

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

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

class Unwind {
private:
  bool b_active_; ///< the thing I want to be restored
  template<typename T>
  class restorer {
    T* ref_;
    T save_;
  public:
    restorer(T* perm) : ref_(perm), save_(*ref_) {};
    ~restorer() { *ref_ = save_; }
  };
public:
  Unwind() : b_active_(false) {};
  void a() { out("a in"); b(); out("a end"); }
  void b() {
    out("b in");
    {
      restorer<bool> trust_in_the_stack(&b_active_); // "restorer" created on the stack
      b_active_ = true; // change b_active_ only while "within" b()
      c();
      out("b inner end");
    }
    out("b end");
  }
  void c() { out("c in"); d(); out("c end"); }
  void d() { out("d in"); cout << "deepest" << endl; out("d end"); }
  void out(const std::string& msg) {
    std::cout << msg << ": " << b_active_ << std::endl;
  }
};

int main() { Unwind u; u.a(); return 0; }

Вывод с использованием g++ 4.2.3 (-Wall):

a in: 0
b in: 0
c in: 1
d in: 1
deepest
d end: 1
c end: 1
b inner end: 1
b end: 0
a end: 0

Это то, что я ожидаю в "b end".

Я чувствовал, что определение восстановителя класса внутри класса Unwind помогает предотвратить неправильное использование.

Мой вопрос: есть ли общий и более безопасный способ сделать это? Меня беспокоят жизненные проблемы.

Изменить: предположим, что в стеке нет потоков, а есть «нисходящие» методы, которые изменяют поведение на основе этого флага b_active_.


person piyo    schedule 16.10.2008    source источник
comment
Я думаю, что было бы лучше заменить save_(*ref_) в списке инициализации на save_(*perm). save_(*ref_) может вас однажды укусить, когда кто-то изменит порядок объявлений в вашем классе.   -  person Maciej Hehl    schedule 17.10.2008
comment
@Maciej H: Хороший вопрос, спасибо!   -  person piyo    schedule 21.10.2008


Ответы (5)


Я согласен с Адамом Пирсом и также думаю, что вы должны предпочесть ссылки указателям:

template<typename T>
class restorer {
   T& ref_;
   T save_;
public:
   restorer(T& perm) : ref_(perm), save_(ref_) {};
   ~restorer() { ref_ = save_; }
};
person dalle    schedule 16.10.2008

Мне нравится шаблон восстановителя, но я, вероятно, поместил бы его вне класса Unwind или даже в отдельный заголовочный файл, чтобы его можно было повторно использовать в других классах в будущем. Это также сделало бы его немного более читабельным.

person Adam Pierce    schedule 16.10.2008

Я бы тоже так поступил. Таким образом, если по какой-то причине функция сбрасывает или возвращается раньше, ваш объект Restorer будет уничтожен, а переменная сброшена до исходного значения. На самом деле вопрос в том, зачем вам нужна переменная, которая возвращается, когда функция возвращается? Используется ли объект из более чем одного потока?

QuantumPete

person Peter Kühne    schedule 16.10.2008

Я немного переработал образец, основываясь на комментариях, и поместил его как ответ вики-сообщества вместо того, чтобы редактировать вопрос.

/// c++ code sample
#ifndef UTIL_RESTORER_HPP
#define UTIL_RESTORER_HPP

namespace Utility {

/// A Restorer instance ("inst") uses the stack to restore a saved
/// value to the named variable when the instance "inst" goes out of
/// scope.
/// 
/// Restorer is designed to be an auto variable, not allocated on any
/// other memory resource like a heap or in-place.
template<typename T>
class restorer {
  T& ref_;
  T  save_;
public:
  restorer(T& perm) : ref_(perm), save_(perm) {}
  ~restorer() { ref_ = save_; }
};

}//NAMESPACE
#endif//UTIL_RESTORER_HPP
person Community    schedule 01.11.2008

Примечание для будущих читателей этой старой темы, а не использовать ее таким образом:

 restorer<bool> trust_in_the_stack(&b_active_);

Поскольку я использую С++ 11, я определил:

 #define restorer_macro(var) restorer<decltype(var)> restorer_##named{var};

Итак, теперь он используется следующим образом:

 restorer_macro(b_active_);

Это позволяет избежать знания типа и именования объекта, поэтому он сохраняется до конца области. Конечно, вы можете предпочесть переименовать восстановитель макросов, а затем переименовать класс, например. реставратор_контейнер.

person J Howe    schedule 24.08.2020