如何重构多余的getter / setter方法?

时间:2014-06-03 11:12:32

标签: c++ templates

我有一个现有的类,其中有互斥锁的成员看起来像这样(类MembersNeedThreadSafe)...

// Just fake a mutex guard interface.  Obviously doesn't do anything
class Mutex
{
  public:
    struct Guard
    {
      Guard(Mutex & M) { };
    };
};

// This is the class I want to redesign.
class MembersNeedThreadSafe {
  Mutex M;

  int i;
  double k;
  // And a dozen more variables

  public:
  void SetI (int foo) { Mutex::Guard G(M); i = foo; }
  int GetI (void) { Mutex::Guard G(M); return i; }

  void SetK (double foo) { Mutex::Guard G(M); k = foo; }
  double GetK (void) { Mutex::Guard G(M); return k; }
  // And two dozen more methods
};

int main (void) {
  MembersNeedThreadSafe bar;

  bar.SetI(5);
  bar.SetK(6.0);

  double d = bar.GetK();

  return 0;
}

我想将类MembersNeedThreadSafe重构为更像这样的设计,但它没有编译,抱怨无效使用非静态数据成员。

template <typename T, Mutex & M> class LockedVar {
  typedef LockedVar<T, M> my_type;
  T value;

  public:
  void Set(T const & foo) { Mutex::Guard G(M); value = foo; }
  T const & Get (void) { Mutex::Guard G(M); return value; }
};

// I want the class to look like this...
class MembersNeedThreadSafe {
  Mutex M;

  public:
  LockedVar <int, M> i;
  LockedVar <double, M> k;
  // And a dozen more variables
};

// This allows the code to run.
int main (void) {
  MembersNeedThreadSafe bar;

  bar.i.Set(5);
  bar.k.Set(6.0);

  double d = bar.k.Get();

  return 0;
}

那么....我怎样才能重构第一个代码块的MembersNeedThreadSafe类,这样我就不必为每个成员编写冗余的getter和setter方法?

附录: 我知道我可以使用这样的设计......

template <typename T> class LockedVar {
  typedef LockedVar<T> my_type;
  T value;
  Mutex & M;

  public:
  LockedVar (Mutex & foo) : M(foo) { }
  void Set(T const & foo) { Mutex::Guard G(M); value = foo; }
  T const & Get (void) { Mutex::Guard G(M); return value; }
};

但是当我这样做时,sizeof(int)== 4,而sizeof(LockedVar)= = 16在我的编译器(gcc 4.8.2)中,它为我抛出一种红旗。看起来我应该能够通过使用互斥量作为模板参数来解决这个问题,并且如果可能的话我想知道如何这样做。

2 个答案:

答案 0 :(得分:3)

另一种选择是稍微修改LockedVar

template <typename T> class LockedVar {
  typedef LockedVar<T> my_type;
  T value;

  public:

  void Set(T const & foo, Mutex & M ) { Mutex::Guard G(M); value = foo; }
  T const & Get (Mutex & M) { Mutex::Guard G(M); return value; }
};

另外,如果你反复重复,虽然看起来不太好看,但你可以创建一个宏:

#define IMPL_SET_GET( t, x ) \
    t x; \
    void Set##x (int foo) { Mutex::Guard G(M); x = foo; } \
    int Get##x (void) { Mutex::Guard G(M); return x; }

// This is the class I want to redesign.
class MembersNeedThreadSafe {
  Mutex M;


  // And a dozen more variables

  public:
      IMPL_SET_GET( int, i );
      IMPL_SET_GET( int, k );

  // And two dozen more methods
};

答案 1 :(得分:2)

首先,您的设计存在问题,锁的粒度允许进行以下交互:

// Thread 1                           // Thread 2
if (x.GetI() != 0) {

                                      x.SetI(0);

    return y / x.GetI();
}

一般来说,您更愿意:

  1. 锁定对象
  2. 执行需要以原子方式完成的所有操作
  3. 解锁对象
  4. 一种简单的方法:

    class Data {
    public:
        friend class Behavior;
    
        Data(): i(3), k(7) {}
    
    private:
        std::mutex mutex;
        int i;
        int k;
    }; // class Data
    
    class Behavior {
    public:
        explicit Behavior(Data& data): ref(data), guard(ref.mutex) {}
    
        int GetI() const { return ref.i; }
        void SetI(int i) { ref.i = i; }
    
        int GetK() const { return ref.k; }
        void SetK(int k) { ref.k = k; }
    
    private:
        Data& ref;
        std::unique_lock<std::mutex> guard;
    }; // class Behavior
    

    请注意,互斥锁只锁定一次(在Behavior的构造函数中),并且您不必在每个getter和setter处重复它。