代理对象/参考getter vs setter?

eol*_*old 7 c++ proxy proxy-classes getter-setter

当我设计一个通用类时,我经常处于以下设计选择之间的两难境地:

template<class T>
class ClassWithSetter {
 public:
  T x() const; // getter/accessor for x
  void set_x(const T& x);
  ...
};
// vs
template<class T>
class ClassWithProxy {
  struct Proxy {
    Proxy(ClassWithProxy& c /*, (more args) */);
    Proxy& operator=(const T& x);  // allow conversion from T
    operator T() const;  // allow conversion to T
    // we disallow taking the address of the reference/proxy (see reasons below)
    T* operator&() = delete;
    T* operator&() const = delete;
    // more operators to delegate to T?
   private:
    ClassWithProxy& c_;
  };
 public:
  T x() const; // getter
  Proxy x();  // this is a generalization of: T& x();
  // no setter, since x() returns a reference through which x can be changed
  ...
}; 
Run Code Online (Sandbox Code Playgroud)

笔记:

  • 为什么我返回的原因T,而不是const T&x()operator T()是因为参照x可能不提供从类中,如果x只存储隐式(如假设T = std::set<int>,但x_类型的T存储为std::vector<int>)
  • 代理的假设高速缓存对象和/或x进行容许

我想知道什么是一些情况,其中一个人更喜欢一种方法而不是另一种方式,尤其是.就......而言:

  • 可扩展性/普遍性
  • 效率
  • 开发人员的努力
  • 用户的努力

您可以假设编译器足够智能以应用NRVO并完全内联所有方法.

目前的个人观察:

(这部分与回答问题无关;它只是作为一种动机,并说明有时一种方法比另一种更好.)

设定方法存在问题的一个特定情况如下.假设您正在实现具有以下语义的容器类:

  • MyContainer<T>& (可变,读写) - 允许修改容器及其数据实现
  • MyContainer<const T>& (可变,只读) - 允许修改容器但不修改其数据
  • const MyContainer<T> (不可变,读写) - 允许修改数据而不是容器
  • const MyContainer<const T> (不可变,只读) - 不修改容器/数据

其中"容器修改"是指添加/删除元素等操作.如果我用setter方法实现这个天真:

template<class T>
class MyContainer {
 public:
   void set(const T& value, size_t index) const {  // allow on const MyContainer&
     v_[index] = value;  // ooops,
     // what if the container is read-only (i.e., MyContainer<const T>)?
   }
   void add(const T& value);  // disallow on const MyContainer&
   ...
 private:
  mutable std::vector<T> v_;
};
Run Code Online (Sandbox Code Playgroud)

通过引入大量依赖于SFINAE的样板代码(例如,通过从实现两个版本的专用模板助手派生)可以减轻这个问题set().然而,更大的问题是,这会制动通用接口,因为我们需要:

  • 确保在只读容器上调用set()是编译错误
  • 为只读容器的set()方法提供不同的语义

另一方面,虽然基于代理的方法工作得很好:

template<class T>
class MyContainer {
   typedef T& Proxy;
 public:
   Proxy get(const T& value, size_t index) const {  // allow on const MyContainer&
     return v_[index];  // here we don't even need a const_cast, thanks to overloading
   }
   ...
};
Run Code Online (Sandbox Code Playgroud)

并且通用接口和语义不会被破坏.

我用代理方法看到的一个难点是支持,Proxy::operator&() 因为可能没有T存储类型的对象/可用的引用(参见上面的注释).例如,考虑:

T* ptr = &x();
Run Code Online (Sandbox Code Playgroud)

除非x_实际存储在某个地方(在类本身中或通过在成员变量上调用的(链)方法可访问),否则不能被支持,例如:

template<class T>
T& ClassWithProxy::Proxy::operator&() {
  return &c_.get_ref_to_x();
}
Run Code Online (Sandbox Code Playgroud)

这是否意味着代理对象引用在T&可用时(即x_显式存储)实际上是优越的,因为它允许:

  • 批处理/延迟更新(例如,想象更改是从代理类析构函数传播的)
  • 更好地控制缓存?

(在这种情况下,两难之间是void set_x(const T& value)T& x().)

编辑:我更改了setter/accessors的constpos中的拼写错误

DaB*_*ain 0

我认为您的 ClassWithProxy 接口混合了包装器/代理和容器。对于容器,通常使用类似的访问器

T& x();
const T& x() const;
Run Code Online (Sandbox Code Playgroud)

就像标准容器一样,例如std::vector::at()。但通常通过引用访问成员会破坏封装。对于容器来说,这是一种便利,也是设计的一部分。

但您注意到对的引用T并不总是可用,因此这将减少 ClassWithSetter 接口的选项,该接口应该是用于处理存储类型的方式的包装器(而容器正在处理存储对象的方式)。T我会更改命名,明确地说,它可能不如普通的获取/设置那么有效。

T load() const;
void save(const T&);
Run Code Online (Sandbox Code Playgroud)

或更多上下文中的内容。现在应该很明显了,T通过使用代理进行修改,再次破坏了封装。

顺便说一句,没有理由不在容器内部使用包装纸。