在设计数据观察类(如迭代器)时,我一直遇到同样的问题,关于const和非const版本的处理和转换。
考虑一个通过指针或类似方法观察一些外部处理数据的类(类似于std::weak_ptr
):
template<typename T>
class observer {
public:
observer(T* ptr) :
_ptr(ptr) {}
T& get() const {
return *_ptr;
}
private:
T* _ptr;
};
除了你只想要访问观察到的数据的时候,这一切都很酷而且花花公子:
template<typename T>
void need_const(observer<T const>) {}
// Uh oh...
observer<int> o{ nullptr };
need_const(o); // Can't do
显然会丢失对const版本的隐式转换,例如所有基本类型(int -> int const
,T* -> T const*
等)。关于如何解决这个问题,我有几个想法,但它们似乎都不理想,我想知道这个问题的C ++风格解决方案是什么样的。
observer<T>
接受observer<U>
的转换构造函数。对于某些情况(并且实际上是std::weak_ptr
中使用的情况)可以正常工作,但显然它会创建一个新对象。它不适用于任何想要通过左值引用访问对象的东西,例如
template<typename T> void need_const(observer<T const>&) {}
。
而是使用两个类observer<T>
和const_observer<T>
,它们分别具有非const和const指针,用于观察数据。这类似于标准库执行迭代器的方式。这仍然无法解决上述问题,导致更多选择如何设计:
observer<T>
包含const_observer<T>
,使用const_cast
来允许非const访问,并为const访问实现operator const_observer<T>&()
转换。完美无缺,除了使用const_cast
让我感到狡猾和不优雅。
observer<T>
继承自const_observer<T>
,使用const_cast
来允许非const访问。同样,这种方法很有效,除了它引入了继承,这是另一个完整的鱼群。
我没有考虑的事情。