General way to reset a member variable to its orig

2019-07-18 09:01发布

问题:

I came across a class instance function that needed to temporarily change a class instance variable, and then restore it when the function completed. The function had return statements all over the place, and before each return there was a restoring statement. That seemed messy to me, not to mention scary when a exception is thrown.

As an improvement I came up with this generalization using a inner class definition. Here is a sample driver program (class restorer).

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; }

The output using g++ 4.2.3 (-Wall) was:

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

Which is what I expect at "b end".

I felt that defining the class restorer inside the class Unwind helps to discourage misuse.

My question is, is there a general and safer way to do this? I am worried about lifetime issues.

Edit: Please assume that there are no threads, but "downstream" methods on the stack that change behavior based on this b_active_ flag.

回答1:

I agree with Adam Pierce and also think that you should prefer references over pointers:

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


回答2:

I like the restorer template but I would probably put the template outside the Unwind class or even in a separate header file so it can be reused by other classes in the future. That would also make it a little more readable.



回答3:

This is how I would do it as well. This way if the function throws, or returns early for some reason, your Restorer object will be destroyed and the variable reset to the original value. The question really is, why do you need to have a variable that is reverted when the function returns? Is the object used from more than one thread?

QuantumPete



回答4:

I revised the sample a bit more based on the comments, and placed as an Community Wiki answer instead of editing the question.

/// 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


标签: c++ callstack