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().然而,更大的问题是,这会制动通用接口,因为我们需要:
另一方面,虽然基于代理的方法工作得很好:
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中的拼写错误
我认为您的 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通过使用代理进行修改,再次破坏了封装。
顺便说一句,没有理由不在容器内部使用包装纸。