3
\$\begingroup\$

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';
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

I guess you're already aware that you'll pay a performance cost for the benefit of hiding the implementation, as each method will be dispatched via a std::function object, and there will be no opportunity for inlining.


It's no longer considered good practice to inherit from std::iterator (and it's deprecated since C++17). Instead, simply declare the types directly in your class:

using iterator_category = std::input_iterator_tag;
using value_type = T;
using difference_type = std::ptrdiff_t;
using pointer = T*;
using reference = T&;

Wrapping the other iterator categories is going to involve more work (you can probably use inheritance for each category to build on its parent category, so you shouldn't have to repeat too much).


InputIteratorDelegate doesn't need two template arguments, as T should always be typename TIterator::value_type.


It's probably worth implementing InputIteratorDelegate::swap(), which will reduce duplication in your constructors and assignment operators.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.