С++ обескураживает базовый класс коллекции - все равно, чтобы подделать его?

Подобной концепции (Java) нет в коллекции С++.

Я могу понять причину, но я хочу знать, есть ли способ подделка. Элегантно.

Пример

Я реализовал множество пользовательских Collection s.
Все они имеют Iterator, который работает правильно, подобно std::vector, std::unordered_set и т.д.

Они MyArray<T>, MyBinaryTree<T> и MySet<T>.

Здесь я покажу рабочий код, который показывает местоположение, которое я хочу подделать.

Скажем, что у меня есть 2 уровня программы: библиотека и пользователь.
Только одно: User команды Library есть все Orange* в ведре.

Library.h

class Library{
    public: static void eatAll(const MyArray<Orange*>& bucket);
};

Library.cpp

#include "Orange.h"
void Library::eatAll(const MyArray<Orange*>& bucket){
    for(auto orange:bucket){
        orange->eaten();
    }
}

user.h

MyArray<Orange*> bucket;
Library::eatAll(bucket);    

Все в порядке.

Теперь я хочу, чтобы Library::eatAll также поддерживал MyBinaryTree<Orange*>, у меня есть несколько не очень желательных подходов, как показано ниже.

Мое плохое решение

1. Java-путь

  • Сделайте MyBinaryTree<T> и MyArray<Orange*> (и их итератор) наследовать от нового класса Collection<T>CollectionIterator<T>).
  • сменить подпись на Library::eatAll(const Collection<T>&)

Недостаток: Снижение производительности от "виртуального" некоторых функций в Collection<T>.

2. Шаблон v1

//Library.h
template<class T> void eatAll(const T&t ){
    for(auto orange : t){
        orange->eaten();
    }
}
  • сделать eatAll функцию шаблона

Недостаток: Реализация eatAll должна быть в заголовке.
Я должен #include orange.h в Library.h.
Иногда я действительно хочу просто отправить декларацию.

3. Шаблон v2

//Library.h
template<class T> void eatAll(const T&t ){
    for(auto orange : t){
        eatAnOrange(orange)
    }
}
private: void eatAnOrange(Orange* orange){
    //below implementation is inside Library.cpp
    orange->eaten();
}
  • создать функцию среднего человека eatAnOrange

Недостаток:

  • Код менее читабельный, а не краткий, вызывает небольшую проблему ремонтопригодности.
  • Если есть много других функций, например. squeezeAll(), мне, вероятно, нужно создать много функций среднего человека, например. squeezeAnOrange().

4. Оператор =()

Создайте конвертер из 3 классов коллекции через неявный конструктор.
Недостаток: Страдает от создания нового экземпляра коллекции.

//Here is what it will do, internally (roughly speaking)
MyBinaryTree<Orange*> bucket;
Library::eatAll(MyArray<Orange*>(bucket)); 

Я считаю, что мои решения неэлегантны.
Существуют ли какие-либо решения, которые не имеют упомянутого недостатка?

Edit:
Оба текущих ответа элегантны, чем мои подходы (спасибо!), Но все еще имеют недостаток: -
- Оливу требуется #include "orange.h" в User.h.
- Ричард Ходжес имеет вызов виртуальной функции.

Ответ 1

В С++ коллекции перемещаются с использованием шаблона проектирования итератора. Вся эта STL разработана вокруг этой концепции. Он может соответствовать вашим потребностям:

Вы можете определить eatAll как функцию, которая принимает два итератора:

template<class Iterator,class Sentinel>
void eatAll(Iterator it, Sentinel s){
    for (;it!=s;++it)
      it->eaten();
}

Или диапазон, как интерфейс алгоритма:

template<class Range>
void eatAll(Range& r){
    for (auto& v:r)
      v.eaten();
}

Вам нужно определить бинарное дерево как диапазон (он должен реализовывать begin() и end()). Надеемся, что деревья - это своего рода граф, который можно линеаризовать. Вся умная работа будет идти в реализации итератора!

Ответ 2

Если вы хотите, чтобы он был действительно полиморфным, мы должны иметь дело с двумя вещами:

  • фактический тип контейнера

  • Тот факт, что результатом разыменования карты является пара, содержащая ссылки на ключевые слова и значения.

Мое мнение состоит в том, что ответ на этот вопрос заключается не в том, чтобы выводить из контейнеров, что является ограничивающим, а для создания полиморфного "итератора значений", который моделирует все итераторы и извлекает их значения правильно.

Затем мы можем написать код следующим образом:

int main()
{

    std::vector<Orange> vo {
            Orange(), Orange()
    };

    std::map<int, Orange> mio {
            { 1, Orange() },
            { 2, Orange() },
            { 3, Orange() }
    };

    std::cout << "vector:\n";
    auto first = makePolymorphicValueIterator(vo.begin());
    auto last = makePolymorphicValueIterator(vo.end());
    do_orange_things(first, last);

    std::cout << "\nmap:\n";
    first = makePolymorphicValueIterator(mio.begin());
    last = makePolymorphicValueIterator(mio.end());
    do_orange_things(first, last);
}

Чтобы получить следующее:

vector:
Orange
Orange

map:
Orange
Orange
Orange

Здесь минимальная полная реализация:

#include <typeinfo>
#include <memory>
#include <iostream>
#include <vector>
#include <map>
#include <iterator>

// define an orange
struct Orange {
};

// a meta-function to get the type of the value of some iterated value_type    
template<class ValueType> struct type_of_value
{
    using type = ValueType;
};

// specialise it for maps and unordered maps
template<class K, class V> struct type_of_value<std::pair<K, V>>
{
    using type = V;
};

template<class ValueType> using type_of_value_t = typename type_of_value<ValueType>::type;

// function to extract a value from an instance of a value_type    
template<class ValueType> struct value_extractor
{
    template<class V>
    auto& operator()(V&& v) const {
        return v;
    }
};

// specialised for maps    
template<class K, class V> struct value_extractor<std::pair<K, V>>
{
    template<class Arg>
    auto& operator()(Arg&& v) const {
        return std::get<1>(v);
    }
};

template<class Iter>
auto extract_value(Iter const& iter) ->  auto&
{
    using value_type = typename std::iterator_traits<Iter>::value_type;
    auto e = value_extractor<value_type> {};
    return e(*iter);
}

// a polymorphic (forward only at the moment) iterator
// which delivers the value (in the case of maps) or the element (every other container)
template<class ValueType>
struct PolymorphicValueIterator {

    using value_type = type_of_value_t<ValueType>;

private:
    struct iterator_details {
        std::type_info const &type;
        void *address;
    };

    struct concept {

        virtual std::unique_ptr<concept> clone() const = 0;

        virtual value_type& invoke_deref() const = 0;

        virtual void invoke_next(std::size_t distance = 1) = 0;

        virtual iterator_details get_details() = 0;

        virtual bool is_equal(const iterator_details &other) const = 0;

        virtual ~concept() = default;

    };

    template<class Iter>
    struct model final : concept {

        model(Iter iter)
                : iter_(iter)
        {}

        std::unique_ptr<concept> clone() const override
        {
            return std::make_unique<model>(iter_);
        }


        virtual value_type& invoke_deref() const override {
            return extract_value(iter_);
        }

        void invoke_next(std::size_t distance = 1) override
        {
            iter_ = std::next(iter_, distance);
        }

        iterator_details get_details() override {
            return {
                    typeid(Iter),
                    std::addressof(iter_)
            };
        }

        bool is_equal(const iterator_details &other) const override {
            if (typeid(Iter) != other.type) {
                return false;
            }
            auto pother = reinterpret_cast<Iter const*>(other.address);
            Iter const& iother = *pother;
            return iter_ == iother;
        }

        Iter iter_;
    };


    std::unique_ptr<concept> concept_ptr_;

public:
    bool operator==(PolymorphicValueIterator const &r) const {
        return concept_ptr_->is_equal(r.concept_ptr_->get_details());
    }

    bool operator!=(PolymorphicValueIterator const &r) const {
        return not concept_ptr_->is_equal(r.concept_ptr_->get_details());
    }

    PolymorphicValueIterator &operator++() {
        concept_ptr_->invoke_next(1);
        return *this;
    }

    value_type& operator*() const {
        return concept_ptr_->invoke_deref();
    }

    template<class Iter>
    PolymorphicValueIterator(Iter iter)
    {
        concept_ptr_ = std::make_unique<model<Iter>>(iter);
    }

    PolymorphicValueIterator(PolymorphicValueIterator const& r)
            : concept_ptr_(r.concept_ptr_->clone())
    {}

    PolymorphicValueIterator& operator=(PolymorphicValueIterator const& r)
    {
        concept_ptr_ = r.concept_ptr_->clone();
        return *this;
    }

};

template<class Iter>
auto makePolymorphicValueIterator(Iter iter)
{
    using iter_value_type = typename std::iterator_traits<Iter>::value_type;
    using value_type = type_of_value_t<iter_value_type>;
    return PolymorphicValueIterator<value_type>(iter);
}

// a test
void do_orange_things(PolymorphicValueIterator<Orange> first, PolymorphicValueIterator<Orange> last)
{
    while(first != last) {
        std::cout << "Orange\n";
        ++first;
    }
}

int main()
{

    std::vector<Orange> vo {
            Orange(), Orange()
    };

    std::map<int, Orange> mio {
            { 1, Orange() },
            { 2, Orange() },
            { 3, Orange() }
    };

    std::cout << "vector:\n";
    auto first = makePolymorphicValueIterator(vo.begin());
    auto last = makePolymorphicValueIterator(vo.end());
    do_orange_things(first, last);

    std::cout << "\nmap:\n";
    first = makePolymorphicValueIterator(mio.begin());
    last = makePolymorphicValueIterator(mio.end());
    do_orange_things(first, last);
}