【发布时间】:2015-05-07 12:49:43
【问题描述】:
背景
这纯粹是出于教育目的。如果你不想阅读整个背景,你可以跳到底部的问题。
我编写了一个 Queue 接口(抽象类),以及 2 个基于调整数组和链表大小的派生实现。
template <typename T>
class IQueue {
public:
virtual void enqueue(T item) = 0;
virtual T dequeue() = 0;
virtual bool isEmpty() = 0;
virtual int size() = 0;
}
template <typename T>
class LinkedListQueue : public IQueue<T> {...}
template <typename T>
class ResizingArrayQueue : public IQueue<T> {...}
我希望能够使用符合 STL 的迭代器遍历队列的元素(我知道队列不应该是可迭代的),因此我可以使用 for (auto e: c) 或 queue.begin() / queue.end()。
因为我使用运行时多态性,所以我必须向IQueue 添加一个客户端迭代器类,并使用 Pimpl 习惯用法在派生队列类中实例化实际实现特定的迭代器,以避免对象切片问题。
所以增强后的代码如下:
template <typename T>
class IQueue {
public:
virtual void enqueue(T item) = 0;
virtual T dequeue() = 0;
virtual bool isEmpty() = 0;
virtual int size() = 0;
public:
class IteratorImpl {
public:
virtual void increment () = 0;
virtual bool operator== (const IteratorImpl& other) const = 0;
virtual bool operator!= (const IteratorImpl& other) const = 0;
virtual T& operator* () const = 0;
virtual T& operator-> () const = 0;
virtual void swap (IteratorImpl& other) = 0;
virtual IteratorImpl* clone() = 0;
};
public:
class ClientIterator : public std::iterator<std::forward_iterator_tag, T> {
std::unique_ptr<IteratorImpl> impl;
public:
ClientIterator(const ClientIterator& other) : impl(other.impl->clone()) {}
ClientIterator(std::unique_ptr<IteratorImpl> it) : impl(std::move(it)) {}
void swap(ClientIterator& other) noexcept {
impl->swap(*(other.impl));
}
ClientIterator& operator++ () {
impl->increment();
return *this;
}
ClientIterator operator++ (int) {
ClientIterator tmp(*this);
impl->increment();
return tmp;
}
bool operator== (const ClientIterator& other) const {
return *impl == *other.impl;
}
bool operator!= (const ClientIterator& other) const {
return *impl != *other.impl;
}
T& operator* () const {
return **impl;
}
T& operator-> () const {
return **impl;
}
};
typedef ClientIterator iterator;
virtual iterator begin() = 0;
virtual iterator end() = 0;
};
其中一个派生类实现了begin() / end() 方法和派生的迭代器实现:
template <typename T>
class LinkedListQueue : public IQueue<T> {
// ... queue implementation details.
public:
class LinkedListForwardIterator : public IQueue<T>::IteratorImpl {
// ... implementation that goes through linked list.
};
typename IQueue<T>::ClientIterator begin() {
std::unique_ptr<LinkedListForwardIterator> impl(new LinkedListForwardIterator(head));
return typename IQueue<T>::iterator(std::move(impl));
}
typename IQueue<T>::ClientIterator end() {
std::unique_ptr<LinkedListForwardIterator> impl(new LinkedListForwardIterator(nullptr));
return typename IQueue<T>::iterator(std::move(impl));
}
};
现在为了测试迭代器是否工作,我有以下 2 个函数:
template <typename T>
void testQueueImpl(std::shared_ptr<IQueue<T> > queue) {
queue->enqueue(1);
queue->enqueue(2);
queue->enqueue(3);
queue->enqueue(4);
queue->enqueue(5);
queue->enqueue(6);
std::cout << "Iterator behavior check 1st: ";
for (auto e: *queue) {
std::cout << e << " ";
}
std::cout << std::endl;
std::cout << "Iterator behavior check 2nd: ";
for (auto it = queue->begin(); it != queue->end(); it++) {
std::cout << *it << " ";
}
}
void testQueue() {
auto queue = std::make_shared<LinkedListQueue<int> >();
testQueueImpl<int>(queue);
auto queue2 = std::make_shared<ResizingArrayQueue<int> >();
testQueueImpl<int>(queue2);
}
问题
我怎样才能摆脱运行时多态性(删除 IQueue,删除迭代器 Pimpl 实现),并重写 testQueue() / testQueueImpl() 函数,以便:
- 这些函数可以成功测试 Stack 实现和 Stack 迭代器,而无需基类指针。
- LinkedListQueue 和 ResizingArrayQueue 都遵循某种编译时接口(存在 enqueue、dequeue、isEmpty、size 方法,存在 begin/end 方法,两个类都包含有效的迭代器类)?
可能的解决方案
对于1)看来我可以简单地将模板参数更改为整个容器,程序编译成功并运行。但这不会检查 begin() / end() / enqueue() 方法是否存在。
对于 2)从我在互联网上可以找到的内容来看,似乎相关的解决方案将涉及 Type Traits / SFINAE / 或概念(容器概念,前向迭代器概念)。 Boost Concepts 库似乎允许注释类以符合容器概念,但我对用于教育目的的自包含解决方案(除了 STL 没有外部库)感兴趣。
template <typename Container>
void testQueueImpl(Container queue) {
queue->enqueue(1);
queue->enqueue(2);
queue->enqueue(3);
queue->enqueue(4);
queue->enqueue(5);
queue->enqueue(6);
std::cout << "Size: " << queue->size() << std::endl;
std::cout << "Iterator behavior check 1st: ";
for (auto e: *queue) {
std::cout << e << " ";
}
std::cout << std::endl;
std::cout << "Iterator behavior check 2nd: ";
for (auto it = queue->begin(); it != queue->end(); it++) {
std::cout << *it << " ";
}
std::cout << std::endl;
}
void testQueue() {
auto queue = std::make_shared<LinkedListQueue<int> >();
testQueueImpl<std::shared_ptr<LinkedListQueue<int> > >(queue);
auto queue2 = std::make_shared<ResizingArrayQueue<int> >();
testQueueImpl<std::shared_ptr<ResizingArrayQueue<int> > >(queue2);
}
【问题讨论】:
-
答案是“使用一个traits类来执行工作,使用外部模板类来定义接口”。我会敲一些东西来演示一下。
-
不确定它是否能解决问题(坦率地说,我没有阅读您的整个帖子),但您可以尝试搜索 tag dispatching。参见例如here
-
Allocators怎么样?您将有两个满足Allocator要求的类,它们在内部具有这两个数据结构并提供添加/删除元素的标准方法;您甚至不需要知道引擎盖下的内容。 -
@Placinta 发布了一个完整的工作示例,说明我将如何处理它。
-
@vsoftco 谢谢,我会读到的。
标签: c++ polymorphism sfinae typetraits c++-concepts