接口和协方差问题 [英] Interfaces and covariance problem

查看:106
本文介绍了接口和协方差问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个特定的类来存储一个数据,它实现了一个接口:

  template< typename T& 
class MyContainer:public Container< T> {
class Something:public IInterface {
public:
// implement *, - >和++这里怎么样?
private:
T x;
};

//在这里实现开始和结束,但是如何实现?

private:
something * data; //数据保存Somethings数组,所以对它们的引用可以从begin()和end()返回到这个数组中的项目,所以接口将工作,但是这使得下面的问题描述
};

我有一个数组 Something



我需要 Something 来实现一个接口类( IInterface 在示例中)其中:


  1. 包含纯虚函数成员函数,返回 retval 返回对 x 成员的引用, retval-> x ++ retval 使 retval

  2. 纯虚拟成员返回的东西可以继承自成员的实现并返回,

  3. container [i] (其中 container code>对象总是返回一些东西,使得 * retval 总是返回一个引用 T

现在,您可以使用该界面如下所示:

  template< typename T> 
class Container {
class IInterface {
public:
virtual T& operator *()= 0;
virtual T * operator->()= 0;
virtual IInterface& operator ++(); //这是问题
};

//现在返回一个引用以支持协方差,所以子类可以
//从Container派生,然后有一个从IInterface
//派生的成员类,并覆盖这些返回它们的派生类,但这有一个问题
virtual IInterface& begin()= 0;
virtual IInterface& end()= 0;
};

我当前的解决方案(使用虚方法返回 IInterface& 并在实现中返回 Something& 没有问题, + retval 需求。因为 Something 直接绑定到它所拥有的对象,不能用指针指向 T 没有办法,我可以找到 ++ 使变量引用数组中的下一个 Something / p>

如果它有助于知道,这是一个迭代器类型的系统。我会使用STL风格的迭代器(你只有一个数组 T ),它们通过值传递并保存指向它们所代表的值的指针,但是会破坏接口,因为只有引用和指针是协变的,并且对象已经存在于别的地方(在我的代码中他们在数组中),所以你不返回对局部对象的引用。



这个设置的目的是为了写一个 Container& 的函数,并迭代容器而不知道什么类型容器是:

  void iterate(Container< int>& somecontainer){
Container< int> IIterator i = somecontainer.begin(); //现在这将返回一个引用,但它不/不能这样工作
while(i!= somecontainer.end()){
doSomething(* i);
++ i; // this is the problem
}
}

解决方案

您想要做的是:称为类型擦除。基本上,您想要提供一个值类型(在整个继承层次结构中是相同的),它包装特定的迭代器类型并提供统一的动态

类型擦除通常使用非虚拟类(类型已擦除)实现,该类存储指向实现擦除的虚拟基类的指针,您可以从中导出用于包装每个特定迭代器的不同类型。静态类将提供模板化的构造函数/赋值运算符,它们将动态地实例化导出类型的对象并在内部存储指针。然后你只需要实现一组操作作为分派到内部对象。



对于最简单的类型擦除形式可能,你可以看看实现 boost :: any (文档 here



草图:

 命名空间detail {
template< typename T>
struct any_iterator_base {
virtual T * operator->()= 0; //正确实现operator->是硬的!
virtual T& operator *()= 0;
virtual any_iterator_base& operator ++()= 0;
};
template< typename T,typename Iterator>
class any_iterator_impl:any_iterator_base {
迭代器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;
}
//实现其他构造函数,包括复制构造
//实现赋值! (三原则)
T& operator *(){
return * it; // virtual dispatch
}
};

实际的实现变得很杂乱。您需要为标准中的不同迭代器类型提供不同版本的迭代器,并且运算符的实现细节可能不是微不足道的。特别是 operator-> 被迭代地应用,直到获得一个原始指针,并且你想确保你的类型擦除的行为不会破坏不变式或文档如何打破适配器可以包装的类型 T 的限制)



对于扩展阅读:
- C ++中面向对象和通用编程之间的紧张关系
- any_iterator:Implementing Erasure for C ++ iterators
- adobe any_iterator


I have a particular class that stores a piece of data, which implements an interface:

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
};

And I have an array of Somethings.

I have the need for Something to implement an interface class (IInterface in the example) which:

  1. Contains pure virtual member functions which return something such that *retval returns a reference to the x member, retval-> returns the address of x, and ++retval makes retval refer to the next Something in the array.
  2. The things that the pure virtual members return can be inherited from and returned by the implementation of the members
  3. container[i] (where container is the array holding the Something objects) always returns something such that *retval always returns a reference to the same T for the same i.

Right now, the interface looks like this:

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;
};

My current solution (have the virtual methods return an IInterface& and return a Something& in the implementation) has no problem with the requirements, except for the ++retval requirement. Because the Something is directly tied to the object it holds and can't point to a T with a pointer, there's no way that I can find to get ++ to make the variable refer to the next Something in the array.

If it helps to know, this is an iterator type system. I would have made it with the STL style iterators (where you just have an array of T) that are passed around by value and hold pointers to the values they represent, but that would break the interface because only references and pointers are covariant, and the objects already have to exist somewhere else already (in my code they're in the array) so you don't return a reference to a local object.

The purpose of this setup is so that one can write functions that take a Container& and iterate the container without knowing what type of container it is:

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
    }
}

It's kind of difficult for me to describe, don't hesitate to let me know if you need more information.

解决方案

What you are trying to do is called type erasure. Basically you want to provide a value type (which is the same across the whole inheritance hierarchy) that wraps the particular iterator type and offers a uniform dynamic interface.

Type erasure is usually implemented with a non-virtual class (the type erased) that stores a pointer to a virtual base class that implements the erasure, from which you derive different types that wrap each particular iterator. The static class would offer templated constructor/assignment operators that would dynamically instantiate an object of the derived type and store the pointer internally. Then you only need to implement the set of operations as dispatch to the internal object.

For the simplest form of type erasure possible, you can take a look at the implementation of boost::any (documentation is here)

Sketch:

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
   }
};

The actual implementation becomes really messy. You need to provide different versions of the iterator for the different iterator types in the standard, and the detail of the implementation of the operators might not be trivial either. In particular operator-> is applied iteratively until a raw pointer is obtained, and you want to make sure that your type erased behavior does not break that invariant or document how you break it (i.e. limitations on the type T that your adaptor can wrap)

For extended reading: - On the Tension Between Object-Oriented and Generic Programming in C++ - any_iterator: Implementing Erasure for C++ iterators - adobe any_iterator ,

这篇关于接口和协方差问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆