【问题标题】:How can two classes with different underlying STL containers present the same iterator interface?具有不同底层 STL 容器的两个类如何呈现相同的迭代器接口?
【发布时间】:2020-11-28 09:57:34
【问题描述】:

我正在实现一个对采样传感器值进行操作的功能。根据获得这些值的方式(从文件读取或实时采样),我想使用不同的底层 STL 容器进行存储(向量和双端队列)。当然,分析逻辑不需要知道底层存储。没有迭代器的设计可能看起来像

class AbstractDataSource
{
   virtual double getValue(const size_t index) const = 0;
};

class FileDataSource : public AbstractDataSource
{
   double getValue(const size_t index) const { return ValueFromVector};
}

class RealtimeDataSource : public AbstractDataSource
{
   double getValue(const size_t index) const { return ValueFromDeqeue};
}

然后将指向 AbstractDataSource 的指针传递给分析部分。

但是,如果 AbstractDataSource 应该有一个类似于 STL 的迭代器接口,带有 begin() 和 end() ,那么如何实现呢?由于迭代器是从 begin() 和 and() 中按值返回的,因此迭代器不能具有纯虚函数。为 AbstractDataSource 定义一种代理迭代器的解决方案是什么?这个代理迭代器有一个指向派生类创建的迭代器的指针。

【问题讨论】:

  • 所以需要在基类中使用迭代器,在派生类中实现?
  • 如果你可以在基类上实现一个虚拟的getValue(index),那么你可以定义一个迭代器,它只保存一个索引和一个AbstractDataSource*指针,并在取消引用时调用getValue。跨度>
  • @IgorTandetnik 是的,这可行,但是如果底层数据存储是随机访问成本高昂的任何东西,例如链表,则遍历值将非常低效。
  • @yao99 不,其他使用基类接口的类应该能够通过迭代器访问数据。
  • 你可以实现一个 type-erased 迭代器类型,它可以容纳任何迭代器类型,或者,如果你只返回 vector 或 deque 迭代器,你可以实现一个 vector_deque_iterator 持有一个std::variant<std::vector<T>::iterator, std::duque<T>::iterator> 并使用std::visit 实现迭代器接口。

标签: c++ stl std


【解决方案1】:

您可以为迭代器创建一个接口,例如:

template <class T>
class ContainerIterator {
public:
    virtual ~ContainerIterator() = default;
    virtual T& operator*() = 0;
    virtual ContainerIterator<T>& operator++() = 0;
    virtual bool operator!=(const ContainerIterator<T>&) const = 0;
    virtual ContainerIterator<T>* copy() = 0;
};

并通过模板为所有类型的迭代器实现接口,如:

template <class T, class I>
class ContainerIteratorImpl: public ContainerIterator<T>{
public:
    I it;
    ContainerIteratorImpl(const I &_it):it(_it) {}
    T& operator*()
    {
        return *it;
    }
    ContainerIterator<T>& operator++()
    {
        ++it;
        return *this;
    }
    bool operator!=(const ContainerIterator<T> &rhs) const
    {
        return it != dynamic_cast<const ContainerIteratorImpl<T,I>&>(rhs).it;
    }
    ContainerIteratorImpl<T,I>* copy()
    {
        return new ContainerIteratorImpl<T,I>(it);
    }
};

但是如果直接使用ContainerIteratorlike:

ContainerIterator<double> it = data.begin();

编译会输出类似error: cannot declare variable ‘it’ to be of abstract type的错误,因为基类ContainerIterator&lt;double&gt;是一个抽象类,即它没有实现,所以我们不能有它的实例。

这是 C++ 变量存储值而不是引用的结果。所以在 C++ 中,我们不能将派生类的实例存储在基类的变量中。例如

Base base = Derived(); // base is a variable

程序调用了Base的拷贝构造函数,而base不是Derived的实例。

但是如果我们使用像这样的指针

Derived derived();
Base *base = &derived; // base is a pointer

base 指向Derived 的一个实例。

所以我们需要为ContainerIterator 创建一个包装器,让我们可以创建一个对象来处理不同的迭代器,例如:

template <class T>
class Iterator {
public:
    ContainerIterator<T> *pit;
    Iterator():pit(0) {}
    Iterator(const Iterator &x):pit(x.pit->copy()) {}
    template <class I>
    Iterator(const I &it):pit(new ContainerIteratorImpl<T,I>(it)){}
    ~Iterator()
    {
        delete pit;
    }
    T& operator*()
    {
        return **pit;
    }
    Iterator& operator++()
    {
        ++*pit;
        return *this;
    }
    bool operator!=(const Iterator &rhs) const
    {
        return *pit != *rhs.pit;
    }
    Iterator& operator=(const Iterator &rhs)
    {
        delete pit;
        pit = rhs.pit->copy();
        return *this;
    }
};

最后,Interator 可以包含不同类型的迭代器。所以你可以在基类中声明begin()和end(),并在派生类中实现:

class AbstractDataSource
{
public:
    virtual ~AbstractDataSource() = default;
    virtual Iterator<double> begin() = 0;
    virtual Iterator<double> end() = 0;
};
class FileDataSource : public AbstractDataSource
{
public:
    std::vector<double> data;
    FileDataSource(const std::vector<double> &_data):data(_data) {}
    Iterator<double> begin()
    {
        return Iterator<double>(data.begin());
    }
    Iterator<double> end()
    {
        return Iterator<double>(data.end());
    }
};
class RealtimeDataSource : public AbstractDataSource
{
public:
    std::deque<double> data;
    RealtimeDataSource(const std::deque<double> &_data):data(_data) {}
    Iterator<double> begin()
    {
        return Iterator<double>(data.begin());
    }
    Iterator<double> end()
    {
        return Iterator<double>(data.end());
    }
};

演示:

#include <cstdio>
#include <vector>
#include <deque>

template <class T>
class ContainerIterator {
public:
    virtual ~ContainerIterator() = default;
    virtual T& operator*() = 0;
    virtual ContainerIterator<T>& operator++() = 0;
    virtual bool operator!=(const ContainerIterator<T>&) const = 0;
    virtual ContainerIterator<T>* copy() = 0;
};

template <class T, class I>
class ContainerIteratorImpl: public ContainerIterator<T>{
public:
    I it;
    ContainerIteratorImpl(const I &_it):it(_it) {}
    T& operator*()
    {
        return *it;
    }
    ContainerIterator<T>& operator++()
    {
        ++it;
        return *this;
    }
    bool operator!=(const ContainerIterator<T> &rhs) const
    {
        return it != dynamic_cast<const ContainerIteratorImpl<T,I>&>(rhs).it;
    }
    ContainerIteratorImpl<T,I>* copy()
    {
        return new ContainerIteratorImpl<T,I>(it);
    }
};

template <class T>
class Iterator {
public:
    ContainerIterator<T> *pit;
    Iterator():pit(0) {}
    Iterator(const Iterator &x):pit(x.pit->copy()) {}
    template <class I>
    Iterator(const I &it):pit(new ContainerIteratorImpl<T,I>(it)){}
    ~Iterator()
    {
        delete pit;
    }
    T& operator*()
    {
        return **pit;
    }
    Iterator& operator++()
    {
        ++*pit;
        return *this;
    }
    bool operator!=(const Iterator &rhs) const
    {
        return *pit != *rhs.pit;
    }
    Iterator& operator=(const Iterator &rhs)
    {
        delete pit;
        pit = rhs.pit->copy();
        return *this;
    }
};

class AbstractDataSource
{
public:
    virtual ~AbstractDataSource() = default;
    virtual Iterator<double> begin() = 0;
    virtual Iterator<double> end() = 0;
};

class FileDataSource : public AbstractDataSource
{
public:
    std::vector<double> data;
    FileDataSource(const std::vector<double> &_data):data(_data) {}
    Iterator<double> begin()
    {
        return Iterator<double>(data.begin());
    }
    Iterator<double> end()
    {
        return Iterator<double>(data.end());
    }
};

class RealtimeDataSource : public AbstractDataSource
{
public:
    std::deque<double> data;
    RealtimeDataSource(const std::deque<double> &_data):data(_data) {}
    Iterator<double> begin()
    {
        return Iterator<double>(data.begin());
    }
    Iterator<double> end()
    {
        return Iterator<double>(data.end());
    }
};

int main(int argc, char *argv[])
{
    AbstractDataSource *fds = new FileDataSource({1,2,3});
    AbstractDataSource *rds = new RealtimeDataSource({4,5,6});
    Iterator<double> it;
    for(it = fds->begin(); it != fds->end(); ++it)
    {
        printf("%f\n",*it);
    }
    for(it = rds->begin(); it != rds->end(); ++it)
    {
        printf("%f\n",*it);
    }
    delete fds;
    delete rds;
    return 0;
}

输出:

1.000000
2.000000
3.000000
4.000000
5.000000
6.000000

【讨论】:

  • 这大概是我会尝试的,但我希望更简单。
  • 如果我在更大的数据样本上尝试此操作,我会遇到内存损坏。注释掉“删除坑”; from ~Iterator 解决了它,但当然会造成内存泄漏。
【解决方案2】:

这是使用Boost.TypeErasure 的解决方案。

#include <algorithm>
#include <iostream>
#include <iterator>
#include <vector>

#include <boost/mpl/vector.hpp>
#include <boost/type_erasure/any.hpp>
#include <boost/type_erasure/operators.hpp>

namespace mpl = boost::mpl;
namespace te  = boost::type_erasure;

template <typename T>
using any_iterator = te::any<
    mpl::vector<te::copy_constructible<>, te::assignable<>, te::incrementable<>,
                te::dereferenceable<T>, te::equality_comparable<>>>;

int main(int, char**) {
  std::vector<int> v{1, 2, 3, 4};
  any_iterator<int> b = v.begin();
  any_iterator<int> e = v.end();
  std::copy(b, e, std::ostream_iterator<int>(std::cout, "\n"));
}

这里,any_iterator 原则上应该模拟forward_iterator。也可以专门化std::iterator_traits 以使value_type 对算法可见。 IT 应该直接将其推广到双向或随机访问迭代器。引擎盖下,应该相当于yao99的解决方案。

【讨论】:

  • 谢谢,我有一些阅读要理解这个解决方案。
猜你喜欢
  • 1970-01-01
  • 2011-05-09
  • 2016-01-06
  • 1970-01-01
  • 1970-01-01
  • 2015-04-29
  • 1970-01-01
  • 2014-08-13
  • 2021-06-27
相关资源
最近更新 更多