4

Background

This is purely for educational purposes. If you don't want to read the whole background, you can skip to the question at the bottom.

I have written a Queue interface (abstract class), and 2 derived implementations based on resizing arrays and linked lists.

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

I wanted to be able to go over a queue's elements with an STL compliant iterator (I know queues should not be iterable), so I can use for (auto e: c) or queue.begin() / queue.end().

Because I use run-time polymorphism I had to add a client iterator class to IQueue and use the Pimpl idiom to instantiate the actual implementation specific iterators in the derived queue classes, to avoid object slicing issue. So the augmented code looks like:

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

and one of the derived classes implements the begin() / end() methods and the derived Iterator implementation:

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

Now in order to test that the iterators work I have the following 2 functions:

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

Question

How can I get rid of the run-time polymorphism (remove IQueue, remove the iterator Pimpl implementations), and rewrite the testQueue() / testQueueImpl() functions so that:

  1. the functions can successfully test the Stack implementations and Stack iterators, without having a base class pointer.
  2. that both LinkedListQueue and ResizingArrayQueue adhere to some kind of a compile-time interface (the enqueue, dequeue, isEmpty, size methods are present, the begin / end methods are present, both classes contain valid iterator classes)?

Possible solution

For 1) it seems that I can simply change the template argument to be the whole container, and the program compiles successfully and runs. But this does not check for the existence of the begin() / end() / enqueue() methods.

For 2) from what I could find on the internet, it seems that the relevant solution would involve Type Traits / SFINAE / or Concepts (Container concept, forward iterator concept). It seems that Boost Concepts library allows annotating a class to conform to a container concept, but I am interested in a self-contained solution (no external libraries except STL) for educational purposes.

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

2 回答 2

2

这是您可能希望如何执行此操作的最小可编译示例。

请注意,目前,此示例仅支持 const begin() 和 const end()。

添加更多方法和可变迭代器是读者的练习

编辑:提供了共享相同策略类的编译时和运行时多态队列的工作示例。

#include <iostream>
#include <list>
#include <vector>
#include <memory>
#include <typeinfo>
#include <typeindex>

/// COMPILE TIME Polymorphic queue of objects of type Element

template<typename Element, class Policy>
struct queue_concept
{
    // Define interface
    struct const_iterator;
    void push_back(Element e);
    const_iterator begin() const;
    const_iterator end() const;


    // Implementation
private:
    Policy _policy;
};

// implement class methods an inner classes

template<typename Element, class Policy>
struct queue_concept<Element, Policy>::const_iterator
{
    using iterator_type = typename Policy::container_type::const_iterator;

    const_iterator(iterator_type iter = iterator_type {})
    : _iter { std::move(iter) }
    {}

    const Element& operator*() const {
        return *_iter;
    }

    const_iterator& operator++() {
        std::advance(_iter, 1);
    }

    bool operator!=(const const_iterator& other) const {
        return _iter != other._iter;
    }

    iterator_type _iter;
};

template<typename Element, class Policy>
void queue_concept<Element, Policy>::push_back(Element e)
{
    _policy._data.push_back(std::move(e));
}

template<typename Element, class Policy>
typename queue_concept<Element, Policy>::const_iterator queue_concept<Element, Policy>::begin() const
{
    return const_iterator { _policy._data.begin() };
}

template<typename Element, class Policy>
typename queue_concept<Element, Policy>::const_iterator queue_concept<Element, Policy>::end() const
{
    return const_iterator { _policy._data.end() };
}

/// RUNTIME Polymorphic queue of objects of type Element
template<typename Element>
struct IQueue
{
    struct const_iterator
    {
        struct Concept {
            // virtual base class so make destructor virtual...
            virtual ~Concept() = default;
            virtual const Element& get_element() const = 0;
            virtual void increment(std::size_t distance) = 0;
            bool equal_to(const Concept& rhs)
            {
                if (this->get_type() == rhs.get_type()) {
                    return unsafe_is_equal(rhs);
                }
                return false;
            }

            virtual bool unsafe_is_equal(const Concept& rhs) const = 0;
            virtual std::type_index get_type() const = 0;

            // provide copy support
            virtual std::unique_ptr<Concept> clone() const = 0;

        };

        template<class Iter>
        struct Model : public Concept {
            Model(Iter iter) : _iter { std::move(iter) }
            {}

            const Element& get_element() const override {
                return *_iter;
            }

            void increment(std::size_t distance) override {
                std::advance(_iter, distance);
            }

            bool unsafe_is_equal(const Concept& rhs) const override {
                auto _rhs = static_cast<const Model&>(rhs);
                return _iter == _rhs._iter;
            }

            std::type_index get_type() const override {
                return std::type_index(typeid(*this));
            }

            std::unique_ptr<Concept> clone() const override {
                return std::unique_ptr<Concept> { new Model(*this) };
            }

        private:
            Iter _iter;    
        };

        // constructor
        template<class Iter>
        const_iterator(Iter iter)
        : _impl { new Model<Iter> { std::move(iter) } }
        {}

        // default constructor - constructs an invalid iterator
        const_iterator()
        {}

        // provide copy support since impl is a unique_ptr
        const_iterator(const const_iterator& other)
        : _impl { other._impl ? other._impl->clone() : std::unique_ptr<Concept>{} }
        {}

        const_iterator& operator=(const_iterator& other)
        {
            auto p = other._impl ? other._impl->clone() : std::unique_ptr<Concept>{};
            std::swap(_impl, p);
        }

        // since we provided copy support we must provide move support
        const_iterator(const_iterator&& rhs) = default;
        const_iterator& operator=(const_iterator&& rhs) = default;

        const Element& operator*() const {
            return _impl->get_element();
        }
        const_iterator& operator++() {
            _impl->increment(1);
            return *this;
        }
        bool operator!=(const const_iterator& rhs) const
        {
            return !(_impl->equal_to(*(rhs._impl)));
        }

    private:
        std::unique_ptr<Concept> _impl;
    };


    virtual void push_back(Element e) = 0;
    virtual const_iterator begin() const = 0;
    virtual const_iterator end() const = 0;
};


template<class Element, class Policy>
struct QueueImpl : public IQueue<Element>
{
    void push_back(Element e) override {
        _policy._data.push_back(std::move(e));
    }

    typename IQueue<Element>::const_iterator begin() const override {
        return typename IQueue<Element>::const_iterator { std::begin(_policy._data) };
    }

    typename IQueue<Element>::const_iterator end() const override {
        return typename IQueue<Element>::const_iterator { std::end(_policy._data) };
    }


    Policy _policy;
};

template<class Element>
struct ResizingArrayPolicy
{
    using container_type = std::vector<Element>;
    container_type _data;
};

template<class Element>
struct LinkedListPolicy
{
    using container_type = std::list<Element>;
    container_type _data;
};

template<class Element>
std::unique_ptr<IQueue<Element>> make_poly_resizing_array_queue()
{
    return std::unique_ptr<IQueue<Element>> { new QueueImpl<Element, ResizingArrayPolicy<Element>> };
}

template<class Element>
std::unique_ptr<IQueue<Element>> make_poly_linked_list_queue()
{
    return std::unique_ptr<IQueue<Element>> { new QueueImpl<Element, LinkedListPolicy<Element>>{} };
}

template<class Element>
queue_concept<Element, ResizingArrayPolicy<Element>> make_static_resizing_array_queue()
{
    return queue_concept<Element, ResizingArrayPolicy<Element>>{};
}

template<class Element>
queue_concept<Element, LinkedListPolicy<Element>> make_static_linked_list_queue()
{
    return queue_concept<Element, LinkedListPolicy<Element>>{};
}

using namespace std;

int main()
{
    // create the queues
    auto pq1 = make_poly_resizing_array_queue<int>();
    auto pq2 = make_poly_linked_list_queue<int>();

    // put data in them    
    pq1->push_back(10);
    pq1->push_back(20);

    pq2->push_back(30);
    pq2->push_back(40);

    // prove that iterators are assignable and moveable
    IQueue<int>::const_iterator it;
    it = pq1->begin();
    cout << *it << endl; // should print 10
    auto i2 = pq2->begin();
    it = move(i2);
    cout << *it << endl; // should print 30

    // prove that queues are polymorphic

    auto queues = vector<unique_ptr<IQueue<int>>>{};
    queues.push_back(move(pq1));
    queues.push_back(move(pq2));

    // print the vector of queues
    for(const auto& queue_ptr : queues) {
        for(const auto& item : *queue_ptr) {
            cout << item << endl;
        }
        cout << endl;
    }

    // now the static versions
    auto q1 = make_static_resizing_array_queue<int>();
    auto q2 = make_static_linked_list_queue<int>();

    q1.push_back(10);
    q1.push_back(20);

    q2.push_back(30);
    q2.push_back(40);

    cout << "static queues\n";
    for(const auto& item : q1) {
        cout << item << endl;
    }
    cout << endl;    
    for(const auto& item : q2) {
        cout << item << endl;
    }

    return 0;
}
于 2015-05-07T13:50:51.727 回答
1

这个问题不清楚您是否真的需要运行时多态性(例如什么?)

一种方法可能类似于 C++ 容器使用的方法:有一个类来管理对象的分配/解除分配和构造/销毁。

template <typename T, class Allocator> 
class Queue
{
     Allocator myAllocator;

 public:
        void enqueue(T item) 
        {
            myAllocator.push(item);
        }

       // other operations.        

};

然后有类似的东西

template <class T, template <typename ...> class Container, class ... Args>
class BasicAllocator
{
      Container<T, Args...> M_list;
public:     
      void push(T element)
      {
          M_list.push_back(element);
      }

      auto begin() -> decltype( std::begin(M_list) )
      { return std::begin(M_list); }

      auto end()   -> decltype( std::end(M_list) )
      { return std::end(M_list); }
};

template<class T>
using LinkedListAllocator = BasicAllocator<T, std::list>;

template<class T>
using LinkedListQueue = Queue<T, LinkedListAllocator<T>>;

实施dequeue可能有点棘手。
活生生的例子

于 2015-05-07T16:03:14.893 回答