C ++:在子类中使用不同的迭代器类型而不破坏继承机制

时间:2012-02-11 10:29:14

标签: c++ templates inheritance iterator

我正在尝试实现以下目标:给定一个抽象类 MemoryObject ,每个类都可以继承,我有两个子类:A Buffer 和一个 BigBuffer

template <typename T>
class MemoryObject
{
public:

    typedef typename std::vector<T>::iterator iterator;
    typedef typename std::vector<T>::const_iterator const_iterator;

    [...] //Lot of stuff

    virtual iterator begin() = 0;
    virtual iterator end() = 0;
};

缓冲区:

template <typename T>
class Buffer: public MemoryObject<T>
{
public:
    typedef typename std::vector<T>::iterator iterator;
    iterator begin() { return buffer_.begin(); }
    iterator end() { return buffer_.end(); };

    [...] //Lot of stuff

private:
    std::vector<T> buffer_;
};

最后:

template <typename T>
class BigBuffer: public MemoryObject<T>
{
public:
    [...] //Omitted, for now

private:
    std::vector<Buffer<T>*> chunks_;
};

如您所见, BigBuffer 拥有缓冲区&lt; T&gt; *的 std :: vector ,因此您可以查看BigBuffer作为Buffer的聚合。此外,我有一堆必须适用于每个MemoryObject的函数,所以这是一个真正的签名:

template <class KernelType, typename T>
void fill(CommandQueue<KernelType>& queue, MemoryObject<T>& obj, const T& value)
{
   //Do something with obj
}

重点是什么? - 你可能会问。关键是我必须在这些类上实现迭代器。我已经为 Buffer 实现了它们,这正是我需要的:能够迭代 Buffer ,并访问范围(例如b.begin() ,b.begin()+ 50)。 显然我不能对 BigBuffer 做同样的事情,因为真实数据(在每个 Buffer '私有变量 buffer _ 中)是分散的记忆。所以我需要一个新类,让我们称之为 BigBufferIterator ,它可以像*或+一样重载运算符,允许我从内存块“跳转”到另一个而不会导致分段错误。

问题是两个:

  1. MemoryObject 的迭代器类型与迭代器不同 BigBuffer 的类型:前者是std :: vector&lt; T&gt; :: iterator, 后一个 BigBufferIterator 。我的编译器显然抱怨
  2. 我希望能够保留我的函数签名的通用性 只向它们传递一个MemoryObject&lt; T&gt;&amp;,而不是专门为它们 每个班级类型。
  3. 我试图解决第一个问题,即添加模板参数分类的迭代器,并为每个类提供一个默认参数,模型松散地基于Alexandrescu的基于策略的模型。这个解决方案解决了第一个问题,但不是第二个问题:我的编译仍然抱怨,告诉我:“不知道从BigBuffer到MemoryObject的转换”,当我尝试将BigBuffer传递给函数时(例如,fill())。这是因为迭代器类型不同..

    我真的很抱歉这首诗,但这是向你提出正确问题的唯一途径。我不知道为什么有人会费心去阅读所有这些东西,但我会带来好运。

    提前致谢,只是为了阅读这一点。

    谦虚地, 阿尔弗雷

2 个答案:

答案 0 :(得分:2)

他们的方法是使用最通用的定义作为基类的迭代器类型。也就是说,您希望将Buffer中的数据视为一个段,而BigBuffer是相应段的序列。这有点不幸,因为它意味着您将Buffer中的单个缓冲区的迭代器视为可能是多个缓冲区,即您只有一个段的分段结构。但是,与替代方案(即具有由句柄包装的虚函数的迭代器的层次结构,给这个混乱提供值语义)相比,你实际上并没有付出不好的代价。

答案 1 :(得分:0)

我在不同的上下文中遇到了同样的问题;让我重申一下。

  1. 你有一个Base类(可以是抽象的),它可以通过BaseIterator进行迭代。
  2. 您有一个Child子类,它的实现略有不同,并且您有不同的专门ChildIterator
  3. 您有自定义函数,可以使用Base ,并依赖于它的可迭代性。
  4. Base的每个子类生成自定义函数的模板专门化是不可行的。可能的原因可能是:
    • 巨大的代码重复;
    • 您将此代码作为库分发,其他开发人员将继承Base;
    • 其他类会对Base进行一些引用或指针并应用这些自定义函数,或者更通用:
    • Base实现了一些逻辑,这些逻辑将在不知道任何子类的上下文中使用(并且编写完全模板化的代码太麻烦)。
  5. 编辑:另一种可能性是使用类型删除。您将隐藏在固定类型的类后面使用的真实迭代器。您必须支付虚拟函数调用的成本。这是一个implementation of a any_iterator类,它实现了完全迭代器类型的擦除,并在其上some more background

    我能找到的唯一有效的解决方案是编写一个多用途迭代器,可以迭代所有可能的容器,可能利用它们的内部(避免复制粘贴迭代器代码的每个子类) Base):

    // A bigger unit of memory
    struct Buffer;
    
    class Iterator {
        // This allows you to know which set of methods you need to call
        enum {
            small_chunks,
            big_chunks
        } const _granularity;
    
        // Merge the data into a union to save memory
        union {
            // Data you need to know to iterate over ints
            struct {
                std::vector<int> const *v;
                std::vector<int>::const_iterator it;
            } _small_chunks;
    
            // Data you need to know to iterate over buffer chunks
            struct {
                std::vector<Buffer *> const *v;
                std::vector<Buffer *>::const_iterator it;
            } _big_chunks;
        };
    
        // Every method will have to choose what to do
        void increment() {
            switch (_granularity) {
                case small_chunks:
                    _small_chunks.it++;
                    break;
                case big_chunks:
                    _big_chunks.it++;
                    break;
            }
        }
    
    public:
        // Cctors are almost identical, but different overloads choose
        // different granularity
        Iterator(std::vector<int> const &container)
            : _granularity(small_chunks) // SMALL
        {
            _small_chunks.v = &container;
            _small_chunks.it = container.begin();
        }
        Iterator(std::vector<Buffer *> const &container)
            : _granularity(big_chunks) // BIG
        {
            _big_chunks.v = &container;
            _big_chunks.it = container.begin();
        }
    
        // ... Implement all your methods
    };
    

    您可以对BaseChild使用相同的类,但需要以不同方式初始化它。此时,您可以使beginend为虚拟,并在每个子类中以不同的方式返回Iterator

    class Base {
    public:
        virtual Iterator begin() const = 0;
    };
    
    class IntChild : public Base {
        std::vector<int> _small_mem;
    public:
        virtual Iterator begin() const {
            // Created with granularity 'small_chunks'
            return Iterator(_small_mem);
        }
        // ...
    };
    
    class BufferChild : public Base {
        std::vector<Buffer *> _big_mem;
    public:
        virtual Iterator begin() const {
            // Created with granularity 'big_chunks'
            return Iterator(_big_mem);
        }
        // ...
    };
    

    您将支付一小笔性能(因为switch语句)和代码重复,但您可以使用<algorithm>中的任何通用算法来使用范围循环,仅在每个函数中使用Base,并且它不会扩展继承机制。

    // Anywhere, just by knowing Base:
    void count_free_chunks(Base &mem) {
        // Thanks to polymorphism, this code will always work
        // with the right chunk size
        for (auto const &item : mem) {
            // ...this works
        }
        // This also works:
        return std::count(mem.begin(), mem.end(), 0);
    }
    

    请注意,此处的关键点是begin()end()必须返回相同的类型。唯一的例外是如果Child的方法会影响Base的方法(通常是not a good practice)。

    一个可能的想法是声明一个抽象迭代器,但这不是那么好。您必须始终使用对迭代器的引用。 Iterator虽然应该作为轻量级类型携带,可分配且易于构造,因此它可以使编码成为一个雷区。