我正在尝试实现以下目标:给定一个抽象类 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 ,它可以像*或+一样重载运算符,允许我从内存块“跳转”到另一个而不会导致分段错误。
问题是两个:
我试图解决第一个问题,即添加模板参数分类的迭代器,并为每个类提供一个默认参数,模型松散地基于Alexandrescu的基于策略的模型。这个解决方案解决了第一个问题,但不是第二个问题:我的编译仍然抱怨,告诉我:“不知道从BigBuffer到MemoryObject的转换”,当我尝试将BigBuffer传递给函数时(例如,fill())。这是因为迭代器类型不同..
我真的很抱歉这首诗,但这是向你提出正确问题的唯一途径。我不知道为什么有人会费心去阅读所有这些东西,但我会带来好运。
提前致谢,只是为了阅读这一点。
谦虚地, 阿尔弗雷
答案 0 :(得分:2)
他们的方法是使用最通用的定义作为基类的迭代器类型。也就是说,您希望将Buffer
中的数据视为一个段,而BigBuffer
是相应段的序列。这有点不幸,因为它意味着您将Buffer
中的单个缓冲区的迭代器视为可能是多个缓冲区,即您只有一个段的分段结构。但是,与替代方案(即具有由句柄包装的虚函数的迭代器的层次结构,给这个混乱提供值语义)相比,你实际上并没有付出不好的代价。
答案 1 :(得分:0)
我在不同的上下文中遇到了同样的问题;让我重申一下。
Base
类(可以是抽象的),它可以通过BaseIterator
进行迭代。 Child
子类,它的实现略有不同,并且您有不同的专门ChildIterator
。Base
,并依赖于它的可迭代性。Base
的每个子类生成自定义函数的模板专门化是不可行的。可能的原因可能是:
Base
; Base
进行一些引用或指针并应用这些自定义函数,或者更通用:Base
实现了一些逻辑,这些逻辑将在不知道任何子类的上下文中使用(并且编写完全模板化的代码太麻烦)。 编辑:另一种可能性是使用类型删除。您将隐藏在固定类型的类后面使用的真实迭代器。您必须支付虚拟函数调用的成本。这是一个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
};
您可以对Base
和Child
使用相同的类,但需要以不同方式初始化它。此时,您可以使begin
和end
为虚拟,并在每个子类中以不同的方式返回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虽然应该作为轻量级类型携带,可分配且易于构造,因此它可以使编码成为一个雷区。