С++ обескураживает базовый класс коллекции - все равно, чтобы подделать его?
Подобной концепции (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);
}