General way to reset a member variable to its original value using the stack?

三世轮回 提交于 2019-12-12 17:25:10

问题


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


来源:https://stackoverflow.com/questions/207965/general-way-to-reset-a-member-variable-to-its-original-value-using-the-stack

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!