Skip to main content
Spelling fix; separate motivation, code and example into sections
Source Link
Toby Speight
  • 81.7k
  • 14
  • 101
  • 308

Exposing Iterators while TrullyTruly Hiding Their Implementation

 
 

Any suggestions/opinions of issues I might have missed?

Thank you

Exposing Iterators while Trully Hiding Their Implementation

Any suggestions/opinions of issues I might have missed?

Thank you

Exposing Iterators while Truly Hiding Their Implementation

 
 
Source Link
D. Jurcau
  • 3k
  • 12
  • 21

Exposing Iterators while Trully Hiding Their Implementation

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

Any suggestions/opinions of issues I might have missed?

Thank you