While implementing classes composed of various collections, I've found that it's pretty hard to completely hide the type of collection used, while still allowing C++ style operations on iterators.
For example, the following two classes allow the client to easily iterate through the collection, but require that all the client code be recompiled, should the collection change from vector to another type.
class FooV1
{
public:
FooV1();
const std::vector<int>& getBars() const;
private:
std::vector<int> _bars;
};
class FooV2
{
public:
FooV2();
std::vector<int>::const_iterator beginBars() const;
std::vector<int>::const_iterator endBars() const;
private:
std::vector<int> _bars;
};
So I've designed a generic iterator wrapper that uses delegates to invoke the actual iterators. Currently, for simplicity, I've only implemented an input iterator.
template<typename T>
class InputIteratorDelegate final : public std::iterator<std::input_iterator_tag, T>
{
public:
typedef void* DelegateType;
typedef std::function<DelegateType(DelegateType)> CopyFuncType;
typedef std::function<void(DelegateType&)> DestructorFuncType;
typedef std::function<bool(DelegateType, DelegateType)> ComparerFuncType;
typedef std::function<void(DelegateType)> PrefixIncrementFuncType;
typedef std::function<const T&(DelegateType)> DereferenceFuncType;
InputIteratorDelegate(DelegateType delegateTo, CopyFuncType copy, DestructorFuncType destructor,
ComparerFuncType comparer, PrefixIncrementFuncType prefixIncrement, DereferenceFuncType dereference) :
_delegateTo(delegateTo), _copy(copy), _destructor(destructor), _comparer(comparer),
_prefixIncrement(prefixIncrement), _dereference(dereference)
{
}
InputIteratorDelegate(const InputIteratorDelegate& other) :
_delegateTo(other._copy(other._delegateTo)), _copy(other._copy), _destructor(other._destructor),
_comparer(other._comparer), _prefixIncrement(other._prefixIncrement), _dereference(other._dereference)
{
}
InputIteratorDelegate(InputIteratorDelegate&& other) :
_delegateTo(other._delegateTo), _copy(std::move(other._copy)), _destructor(std::move(other._destructor)),
_comparer(std::move(other._comparer)), _prefixIncrement(std::move(other._prefixIncrement)),
_dereference(std::move(other._dereference))
{
other._delegateTo = nullptr;
}
~InputIteratorDelegate()
{
if (_destructor && _delegateTo)
{
_destructor(_delegateTo);
}
}
InputIteratorDelegate& operator=(const InputIteratorDelegate& other)
{
InputIteratorDelegate temp(other);
*this = std::move(temp);
return *this;
}
InputIteratorDelegate& operator=(InputIteratorDelegate&& other)
{
_delegateTo = other._delegateTo;
other._delegateTo = nullptr;
_copy = std::move(other._copy);
_destructor = std::move(other._destructor);
_comparer = std::move(other._comparer);
_prefixIncrement = std::move(other._prefixIncrement);
_dereference = std::move(other._dereference);
return *this;
}
bool operator==(const InputIteratorDelegate& other) const
{
return _comparer(_delegateTo, other._delegateTo);
}
bool operator!=(const InputIteratorDelegate& other) const
{
return !(*this == other);
}
InputIteratorDelegate<T>& operator++()
{
_prefixIncrement(_delegateTo);
return *this;
}
InputIteratorDelegate<T> operator++(int)
{
auto current = *this;
++*this;
return current;
}
const T& operator*() const
{
return _dereference(_delegateTo);
}
private:
DelegateType _delegateTo;
CopyFuncType _copy;
DestructorFuncType _destructor;
ComparerFuncType _comparer;
PrefixIncrementFuncType _prefixIncrement;
DereferenceFuncType _dereference;
};
template<typename T>
class InputIteratorWrapper final
{
public:
InputIteratorWrapper(InputIteratorDelegate<T> begin, InputIteratorDelegate<T> end) :
_begin(std::move(begin)), _end(std::move(end))
{
}
InputIteratorWrapper(const InputIteratorWrapper& other) :
_begin(other._begin), _end(other._end)
{
}
InputIteratorWrapper(InputIteratorWrapper&& other) :
_begin(std::move(other._begin)), _end(std::move(other._end))
{
}
InputIteratorDelegate<T> begin() const
{
return _begin;
}
InputIteratorDelegate<T> end() const
{
return _end;
}
private:
InputIteratorDelegate<T> _begin;
InputIteratorDelegate<T> _end;
};
This allows me to use templates to generate the actual delegates, without requiring explicit code for each type of container
template<typename T, typename TIterator>
InputIteratorDelegate<T> make_input_iterator_delegate(const TIterator& iterator)
{
auto iteratorCopy = new TIterator(iterator);
auto copy = [](typename InputIteratorDelegate<T>::DelegateType pointer) ->
typename InputIteratorDelegate<T>::DelegateType
{
if ( ! pointer)
{
return nullptr;
}
auto it = reinterpret_cast<TIterator*>(pointer);
return new TIterator(*it);
};
auto destructor = [](typename InputIteratorDelegate<T>::DelegateType& pointer)
{
auto it = reinterpret_cast<TIterator*>(pointer);
delete it;
pointer = nullptr;
};
auto comparer = [](typename InputIteratorDelegate<T>::DelegateType first,
typename InputIteratorDelegate<T>::DelegateType second)
{
auto itFirst = reinterpret_cast<TIterator*>(first);
auto itSecond = reinterpret_cast<TIterator*>(second);
return *itFirst == *itSecond;
};
auto prefixIncrement = [](typename InputIteratorDelegate<T>::DelegateType pointer)
{
auto it = reinterpret_cast<TIterator*>(pointer);
++(*it);
};
auto dereference = [](typename InputIteratorDelegate<T>::DelegateType pointer) -> const T&
{
auto it = reinterpret_cast<TIterator*>(pointer);
return **it;
};
return InputIteratorDelegate<T>(iteratorCopy, copy, destructor, comparer, prefixIncrement, dereference);
}
The initial class can now take advantage of this with minimal code
Header
class FooV3
{
public:
FooV3();
virtual ~FooV3();
InputIteratorWrapper<int> getBars() const;
private:
struct Private;
Private* _private;
};
Source
struct FooV3::Private
{
std::vector<int> bars;
};
FooV3::FooV3() : _private(new Private())
{
_private->bars = { 1, 2, 3 };
}
FooV3::~FooV3()
{
delete _private;
}
InputIteratorWrapper<int> FooV3::getBars() const
{
return InputIteratorWrapper<int>(make_input_iterator_delegate<int>(_private->bars.cbegin()),
make_input_iterator_delegate<int>(_private->bars.cend()));
}
Usage
FooV3 v3;
for (auto& item : v3.getBars())
{
std::cout << item << ' ';
}
std::cout << '\n';