接口与协方差问题
Interfaces and covariance problem
我有一个存储一段数据的特殊类,它实现了一个接口:
template<typename T>
class MyContainer : public Container<T> {
class Something : public IInterface {
public:
// implement *, ->, and ++ here but how?
private:
T x;
};
// implement begin and end here, but how?
private:
Something* data; // data holds the array of Somethings so that references to them can be returned from begin() and end() to items in this array so the interface will work, but this makes the problem described below
};
我有一个Something
s的数组
我需要Something
来实现一个接口类(在示例中为IInterface
),其:
- 包含纯虚成员函数,这些函数返回
*retval
对x
成员的引用,retval->
返回x
的地址,++retval
使retval
指向数组中的下一个Something
。 - 纯虚成员返回的东西可以继承,并由成员 的实现返回。
-
container[i]
(其中container
是保存Something
对象的数组)总是返回一些东西,使得*retval
总是返回对相同i
的相同T
的引用。
现在,界面看起来是这样的:
template<typename T>
class Container {
class IInterface {
public:
virtual T& operator*() = 0;
virtual T* operator->() = 0;
virtual IInterface& operator++(); // this is the problem
};
// returning a reference right now to support covariance, so subclasses can
// derive from Container and then have a member class derive from IInterface
// and override these to return their derived class, but this has a problem
virtual IInterface& begin() = 0;
virtual IInterface& end() = 0;
};
我目前的解决方案(让虚拟方法返回IInterface&
并在实现中返回Something&
)与要求没有问题,除了用于++retval
要求。因为Something
直接绑定到它所持有的对象,不能用指针指向T
,所以我无法找到让++
使变量指向数组中的下一个Something
的方法。
如果有帮助的话,这是一个迭代器类型系统。我本可以用STL风格的迭代器(你只是有一个T
数组),通过值传递,并保持指针指向它们所代表的值,但这会破坏接口,因为只有引用和指针是协变的,对象已经存在于其他地方了(在我的代码中,它们在数组中),所以你不返回对本地对象的引用。
这样设置的目的是为了编写接受Container&
并迭代容器的函数,而不知道它是什么类型的容器:
void iterate(Container<int>& somecontainer) {
Container<int>::IIterator i = somecontainer.begin(); // right now this would return a reference, but it doesn't/can't work that way
while (i != somecontainer.end()) {
doSomething(*i);
++i; // this is the problem
}
}
这对我来说很难描述,如果你需要更多的信息,请不要犹豫,告诉我。
您要做的事情叫做类型擦除。基本上,您需要提供一个值类型(在整个继承层次结构中都是一样的),它包装了特定的迭代器类型,并提供了一个统一的动态接口。
类型擦除通常使用非虚类(类型擦除)实现,该类存储指向实现擦除的虚基类的指针,从中派生出包装每个特定迭代器的不同类型。静态类将提供模板化的构造函数/赋值操作符,这些操作符将动态地实例化派生类型的对象并在内部存储指针。然后,您只需要将操作集作为分派实现到内部对象。
对于最简单的类型擦除形式,您可以查看boost::any
的实现(文档在这里)
素描:
namespace detail {
template<typename T>
struct any_iterator_base {
virtual T* operator->() = 0; // Correct implementation of operator-> is tough!
virtual T& operator*() = 0;
virtual any_iterator_base& operator++() = 0;
};
template <typename T, typename Iterator>
class any_iterator_impl : any_iterator_base {
Iterator it;
public:
any_iterator_impl( Iterator it ) : it(it) {}
virtual T& operator*() {
return *it;
}
any_iterator_impl& operator++() {
++it;
return *this;
}
};
}
template <typename T>
class any_iterator {
detail::any_iterator_base<T>* it;
public:
template <typename Iterator>
any_iterator( Iterator it ) : it( new detail::any_iterator_impl<T,Iterator>(it) ) {}
~any_iterator() {
delete it;
}
// implement other constructors, including copy construction
// implement assignment!!! (Rule of the Three)
T& operator*() {
return *it; // virtual dispatch
}
};
实际的实现变得非常混乱。您需要为标准中的不同迭代器类型提供不同版本的迭代器,并且操作符实现的细节也可能不是微不足道的。特别是operator->
被迭代地应用,直到获得原始指针,并且你想确保你的类型擦除行为不会破坏该不变量或记录你如何破坏它(即对适配器可以包装的T
类型的限制)
用于扩展阅读:论c++中面向对象编程与泛型编程的矛盾- any_iterator:为c++迭代器实现Erasure- adobe any_iterator,
我建议你看看Visitor
模式。
除此之外,你想要的是一个将被注入多态行为的值类型。有一个比James使用你的IInterface
更简单的解决方案。
class IInterface
{
virtual ~IInterface() {}
virtual void next() = 0;
virtual void previous() = 0;
virtual T* pointer() const = 0;
virtual std::unique_ptr<IInterface> clone() const = 0;
};
std::unique_ptr<IInterface> clone(std::unique_ptr<IInterface> const& rhs) {
if (!rhs) { return std::unique_ptr<IInterface>(); }
return rhs->clone();
}
class Iterator
{
friend class Container;
public:
Iterator(): _impl() {}
// Implement deep copy
Iterator(Iterator const& rhs): _impl(clone(rhs._impl)) {}
Iterator& operator=(Iterator rhs) { swap(*this, rhs); return *this; }
friend void swap(Iterator& lhs, Iterator& rhs) {
swap(lhs._impl, rhs._impl);
}
Iterator& operator++() { assert(_impl); _impl->next(); return *this; }
Iterator& operator--() { assert(_impl); _impl->previous(); return *this; }
Iterator operator++(int); // usual
Iterator operator--(int); // usual
T* operator->() const { assert(_impl); return _impl->pointer(); }
T& operator*() const { assert(_impl); return *_impl->pointer(); }
private:
Iterator(std::unique_ptr<IInterface> impl): _impl(impl) {}
std::unique_ptr<IInterface> _impl;
};
最后,Container
类将提出:
protected:
virtual std::unique_ptr<IInterface> make_begin() = 0;
virtual std::unique_ptr<IInterface> make_end() = 0;
和实施:
public:
Iterator begin() { return Iterator(make_begin()); }
Iteraotr end() { return Iterator(make_end()); }
注意:
如果可以避免所有权问题,可以取消std::unique_ptr
。如果您可以将IInterface限制为仅用于行为(通过将状态提取到Iterator
中),那么您就可以启用Strategy
模式,并在静态分配对象中使用指针。这样可以避免动态分配内存。
当然,这意味着你的迭代器不会那么丰富,因为它要求IInterface
实现是无状态的,并且实现"过滤"迭代器,例如,将变得不可能。
您考虑过使用CRTP吗?我觉得它在这里很合适。下面是一个简短的演示。它只是解释了你的++retval
问题(如果我理解正确的话)。您必须将IInterface
定义从pure virtual
更改为CRTP类型接口。
template<class Derived>
struct IInterface
{
Derived& operator ++ ()
{
return ++ *(static_cast<Derived*>(this));
}
};
struct Something : public IInterface<Something>
{
int x;
Something& operator ++ ()
{
++x;
return *this;
}
};
CRTP有一些限制,template
总是跟随你的IInterface
。这意味着如果您将Something
对象传递给这样的函数:
foo(new Something);
那么,foo()
应该定义为:
template<typename T>
void foo(IInterface<T> *p)
{
//...
++(*p);
}
然而,对于你的问题,它可能是一个很好的选择。
就像你说的,问题是Something
的实例被绑定到它所持有的对象。所以让我们试着解开它们。
Something
实例都绑定有一个可公开访问的数据成员T x
。如果不这样做,最好对其进行抽象,即提供访问器方法,而不是:
class Something : IInterface
{
private:
T x;
public:
T GetX()
{
return x;
}
};
现在用户已经知道x
是什么类型的东西,更不用说x
的存在了。
这是很好的第一步,但是,因为您希望能够让x
在不同的时间引用不同的对象,我们几乎必须将x
设置为指针。作为对传统代码的让步,我们还将使GetX()
返回一个const引用,而不是一个常规值:
class Something: IInterface
{
private:
T *x;
public:
T const& GetX()
{
return *x;
}
};
现在实现IInterface
中的方法很简单:
class Something: IInterface
{
private:
T *x;
public:
T const& GetX()
{
return *x;
}
T& operator*()
{
return *x;
}
T* operator->()
{
return x;
}
Something& operator++()
{
++x;
return *this;
}
};
++
运算符现在是微不足道的-它实际上只是将++
应用于x
。
用户现在不知道指针被使用了。他们只知道他们的代码工作正常。这是OOP数据抽象原则中最重要的一点。
编辑
至于实现Container
的begin
和end
方法,这应该也不是太困难,但它需要对Container
进行一些更改。
首先,让我们为Something
添加一个私有构造函数,它接受指向起始对象的指针。我们还将使MyContainer
成为Something
的朋友:
类Something: IInterface{
friend class MyContainer; // Can't test the code right now - may need to be MyContainer<T> or ::MyContainer<T> or something.
private:
T *x;
Something( T * first )
: x(first)
{
}
public:
T const& GetX()
{
return *x;
}
T& operator*()
{
return *x;
}
T* operator->()
{
return x;
}
Something& operator++()
{
++x;
return *this;
}
};
通过将构造函数设为私有,并设置友元依赖,可以确保只有 MyContainer可以创建新的Something
迭代器(这可以保护我们在用户给出错误时在随机内存上迭代)。
接下来,我们稍微改变一下MyContainer,这样我们就不会有一个Something
的数组,而是一个T
的数组:
class MyContainer
{
...
private:
T *data;
};
在我们实现begin
和end
之前,让我们对Container
进行更改:
template<typename T, typename IteratorType>
class Container {
public:
...
// These prototype are the key. Notice the return type is IteratorType (value, not reference)
virtual IteratorType begin() = 0;
virtual IteratorType end() = 0;
};
因此,我们不是依靠协方差(在这种情况下,真的很难),而是使用一点模板魔法来做我们想做的事情。
当然,由于容器现在接受另一个类型参数,我们需要对MyContainer
进行相应的修改;也就是说,我们需要将Something
作为类型参数提供给Container
:
template<class T>
class MyContainer : Container<T, Something>
...
和begin
/end
方法现在很容易:
template<class T>
MyContainer<T>::begin()
{
return Something(data);
}
template<class T>
MyContainer<T>::end()
{
// this part depends on your implementation of MyContainer.
// I'll just assume your have a length field in MyContainer.
return Something(data + length);
}
这就是我午夜的想法。就像我上面提到的,我目前无法测试这段代码,所以您可能需要稍微调整一下。希望这能满足你的要求。
如果使用方法与stdlib类似,则迭代器需要是值对象,因为它通常会被值复制很多次。(另外,否则begin
和end
方法返回的引用是什么?)
template <class T>
class Iterator
{
shared_ptr<IIterator> it;
public:
Iterator(shared_ptr<IIterator>);
T& operator*() { it->deref(); }
T* operator->() { return &it->deref(); }
Iterator& operator++() { it->inc(); return *this; }
etc.
};
- 为什么是谷神星协方差.计算()似乎永远运行而不返回?
- 为什么需要返回指针来利用协方差?
- Eigen对修复非正定义的协方差矩阵有解吗
- 回调参数中的协方差C++
- 获取长双精度向量的方差
- 我在计算 4 个值的方差时的错误在哪里
- C++容器、协方差和模板
- "shared_ptr"如何实现协方差?
- C++协方差返回类型的缺点是什么
- 我遇到了一个关于多线程的小问题.需要多线程来计算 Pi 和方差
- 如何在犰狳中使用变量/方差函数
- 用c++计算平均值和方差
- 如何实现支持模板协方差的通用工厂
- C 协方差意外行为
- 练习:使用数组计算方差
- 带有指针返回问题的c++协方差问题
- 计算OpenCV中的协方差
- 使用 OpenCV 计算协方差矩阵
- 接口与协方差问题
- 一个c++协方差/覆盖/循环问题