【问题标题】:How to remove code duplication const_iterator and iterator in my case?在我的情况下,如何删除代码重复 const_iterator 和迭代器?
【发布时间】:2021-09-28 05:18:36
【问题描述】:

我查阅了很多答案,但我仍然没有找到最好的实现方法。

现在我的情况是实现一个多地图容器,代码如下:

    #include <unordered_map>
    #include <utility>
    
    static const size_t MAP_NUM_BITS = 5;
    static const size_t MAP_NUM = (size_t)1 << MAP_NUM_BITS;
    
    
    template<typename K, typename V>
    class MultiMap {
      using MapType = std::unordered_map<K, V>;
      using MapIter = typename MapType::iterator;
      using MapRef = typename MapType::reference;
    
    public:
      struct iterator {
        MapIter it;
        size_t idx;
        MapType *multi_maps;
    
        bool operator==(const iterator &rhs) const {
          return it == rhs.it;
        }
    
        bool operator!=(const iterator &rhs) const {
          return it != rhs.it;
        }
    
        iterator& operator++() {
          ++it;
          while (it == multi_maps[idx].end() &&
                 idx + 1 < MAP_NUM) {
            it = multi_maps[++idx].begin();
          }
          return *this;
        }
    
        iterator operator++(int) {
          iterator ret = *this;
          ++*this;
          return ret;
        }
    
        MapIter& operator->() {
          return it;
        }
    
        MapRef operator*() {
          return *it;
        }
      };
    
      iterator begin() {
        size_t idx = 0;
        auto it = multi_maps_[idx].begin();
        while (it == multi_maps_[idx].end() &&
               idx + 1 < MAP_NUM) {
          it = multi_maps_[++idx].begin();
        }
        return {it, idx, multi_maps_};
      }
    
      iterator end() {
        return {multi_maps_[MAP_NUM - 1].end(), MAP_NUM - 1, multi_maps_};
      }
    
      iterator find(const K &key) {
        size_t hash = hasher_(key);
        size_t idx = compute_idx(hash);
        auto it = multi_maps_[idx].find(key);
        if (it == multi_maps_[idx].end()) {
          return end();
        }
        return {it, idx, multi_maps_};
      }
    
      std::pair<iterator, bool> insert(const std::pair<K, V> &data) {
        size_t hash = hasher_(data.first);
        size_t idx = compute_idx(hash);
        auto res = multi_maps_[idx].insert(data);
        return {{res.first, idx, multi_maps_}, res.second};
      }
    
      size_t size() const {
        size_t total_size = 0;
        for (auto &m : multi_maps_) {
          total_size += m.size();
        }
        return total_size;
      }
    
      void clear() {
        for (auto &m : multi_maps_) {
          m.clear();
        }
      }
    
    private:
      size_t compute_idx(size_t hash) const {
        if (MAP_NUM == 1) {
          return 0;
        } else {
          return hash >> (sizeof(size_t) * 8 - MAP_NUM_BITS);
        }
    
      }
    
    private:
      MapType multi_maps_[MAP_NUM];
      std::hash<K> hasher_;
    };

现在它可以在我的测试中使用:

#include <iostream>
#include "multi_map.h"

int main() {

  MultiMap<int, int> mm;
  for (size_t i = 0; i < 5; ++i) {
    mm.insert({2*i, i*i});
  }

  std::cout << "Map size is: " << mm.size() << std::endl;

  for (size_t i = 0; i < 10; ++i) {
    auto it = mm.find(i);
    if (it != mm.end()) {
      std::cout << "Found: " << i << std::endl;
    } else {
      std::cout << "Not found: " << i << std::endl;
    }
  }

  for (auto it = mm.begin(); it != mm.end(); ++it) {
    std::cout << it->first << " : " << it->second << std::endl;
  }

  return 0;
}

但是当它引用 const 类型时,事情就变糟了!

所以我要实现一个const_iterator,所以代码是:

include <unordered_map>
#include <utility>

static const size_t MAP_NUM_BITS = 5;
static const size_t MAP_NUM = (size_t)1 << MAP_NUM_BITS;


template<typename K, typename V>
class MultiMap {
  using MapType = std::unordered_map<K, V>;
  using MapIter = typename MapType::iterator;
  using MapConstIter = typename MapType::const_iterator;
  using MapRef = typename MapType::reference;
  using MapConstRef = typename MapType::const_reference;

public:
  struct iterator {
    MapIter it;
    size_t idx;
    MapType *multi_maps;

    bool operator==(const iterator &rhs) const {
      return it == rhs.it;
    }

    bool operator!=(const iterator &rhs) const {
      return it != rhs.it;
    }

    iterator& operator++() {
      ++it;
      while (it == multi_maps[idx].end() &&
             idx + 1 < MAP_NUM) {
        it = multi_maps[++idx].begin();
      }
      return *this;
    }

    iterator operator++(int) {
      iterator ret = *this;
      ++*this;
      return ret;
    }

    MapIter& operator->() {
      return it;
    }

    MapRef operator*() {
      return *it;
    }
  };

  struct const_iterator {
     MapConstIter it;
     size_t idx;
     const MapType *multi_maps;

     bool operator==(const const_iterator &rhs) const {
       return it == rhs.it;
     }

     bool operator!=(const const_iterator &rhs) const {
       return it != rhs.it;
     }

     const_iterator& operator++() {
       ++it;
       while (it == multi_maps[idx].end() &&
              idx + 1 < MAP_NUM) {
         it = multi_maps[++idx].begin();
       }
       return *this;
     }

     const_iterator operator++(int) {
       const_iterator ret = *this;
       ++*this;
       return ret;
     }

     MapConstIter& operator->() {
       return it;
     }

     MapConstRef operator*() {
       return *it;
     }

  };

  iterator begin() {
    size_t idx = 0;
    auto it = multi_maps_[idx].begin();
    while (it == multi_maps_[idx].end() &&
           idx + 1 < MAP_NUM) {
      it = multi_maps_[++idx].begin();
    }
    return {it, idx, multi_maps_};
  }

  const_iterator begin() const {
    size_t idx = 0;
    auto it = multi_maps_[idx].begin();
    while (it == multi_maps_[idx].end() &&
           idx + 1 < MAP_NUM) {
      it = multi_maps_[++idx].begin();
    }
    return {it, idx, multi_maps_};
  }

  iterator end() {
    return {multi_maps_[MAP_NUM - 1].end(), MAP_NUM - 1, multi_maps_};
  }

  const_iterator end() const {
    return {multi_maps_[MAP_NUM - 1].end(), MAP_NUM - 1, multi_maps_};
  }

  iterator find(const K &key) {
    size_t hash = hasher_(key);
    size_t idx = compute_idx(hash);
    auto it = multi_maps_[idx].find(key);
    if (it == multi_maps_[idx].end()) {
      return end();
    }
    return {it, idx, multi_maps_};
  }

  const_iterator find(const K &key) const {
    size_t hash = hasher_(key);
    size_t idx = compute_idx(hash);
    auto it = multi_maps_[idx].find(key);
    if (it == multi_maps_[idx].end()) {
      return end();
    }
    return {it, idx, multi_maps_};
  }

  std::pair<iterator, bool> insert(const std::pair<K, V> &data) {
    size_t hash = hasher_(data.first);
    size_t idx = compute_idx(hash);
    auto res = multi_maps_[idx].insert(data);
    return {{res.first, idx, multi_maps_}, res.second};
  }

  size_t size() const {
    size_t total_size = 0;
    for (auto &m : multi_maps_) {
      total_size += m.size();
    }
    return total_size;
  }

  void clear() {
    for (auto &m : multi_maps_) {
      m.clear();
    }
  }

private:
  size_t compute_idx(size_t hash) const {
    if (MAP_NUM == 1) {
      return 0;
    } else {
      return hash >> (sizeof(size_t) * 8 - MAP_NUM_BITS);
    }

  }

private:
  MapType multi_maps_[MAP_NUM];
  std::hash<K> hasher_;
};

代码重复难以忍受。

对于 const 和 non-const 函数,const_cast 会有所帮助:

const_iterator begin() const {
  return const_cast<MultiMap *>(*this)->begin();
}

为此,我必须在 const_iterator 中添加一个构造:

const_iterator(const iterator &it) {
  this->it = it.it;
  this->idx = it.idx;
  this->multi_maps = it.multi_maps;
}

这里的代码重复太多了。

在我的情况下,是否有避免代码重复的最佳解决方案?

【问题讨论】:

  • 我通常会创建一个template&lt;class Type&gt; class iterator_impl;using iterator = iterator_impl&lt;T&gt;; using const_iterator = iterator_impl&lt;const T&gt;; - 如果我认为这很有意义,我会这样做,以便iterator 可以转换为const_iterator,但不能反过来。
  • @TedLyngmo 是的,template_impl 是个好主意,但是在函数const_iterator begin() const 中,如何将non-const begin() 返回的迭代器转换为const_iterator?
  • 一个转换构造函数是enable_ifd 仅适用于const_iterator(const interator&amp;); 将是一种选择。如果iterator_impl 成员都是public,则不需要friend 关系,否则,您需要。
  • @TedLyngmo 哇,我认为这是个好主意。不过很抱歉打扰你,我对C++不是很熟悉,你能给我举个例子吗?
  • 一个问题:为什么会这样? std::unordered_map&lt;K, V&gt;::iterator&amp; operator-&gt;(); 我期待像 std::unordered_map&lt;K, V&gt;::value_type* operator-&gt;(); 这样的东西,因为 -&gt; 的特殊行为,它可能会起作用,但它看起来确实让读者有点困惑 :)

标签: c++


【解决方案1】:

一种方法是在const_iterator 中实现批量并让iterator 从那里继承。一定需要一些const_casts。

另一种方法是将迭代器实现为模板,iterator_impl&lt;&gt;,然后添加两个typedefs:

using iterator = iterator_impl<value_type>;
using const_iterator = iterator_impl<const value_type>;

我在这个答案中选择了后者,并且可以将iterator 转换为const_iterator,但不能反过来。我已经评论了我对代码的更改:

#include <cstddef>
#include <iterator>
#include <numeric>
#include <unordered_map>
#include <utility>

template<typename K, typename V>
class MultiMap {
private:
    // these constants can be hidden in here as private:
    static constexpr size_t MAP_NUM_BITS = 5;
    static constexpr size_t MAP_NUM = 1ULL << MAP_NUM_BITS;

    using MapType = std::unordered_map<K, V>;

public:
    // some of the common public typedef's one expects:
    using value_type = typename MapType::value_type;
    using reference = typename MapType::reference;
    using const_reference = typename MapType::const_reference;
    using pointer = typename MapType::pointer;
    using const_pointer = typename MapType::const_pointer;

private:
    using MapIter = typename MapType::iterator;
    using MapConstIter = typename MapType::const_iterator;

    // The iterator template - it's private since it doesn't need to be
    // seen from the outside.

    template<class Type>
    struct iterator_impl {
        // Added a default and a converting constructor - this became necessary
        // when I added the constructor to convert from an iterator to a const_iterator
        iterator_impl() = default;
        iterator_impl(MapIter It, size_t Idx, MapType* mm) :
            it(It), idx(Idx), multi_maps(mm) {}

        // Convert from iterator to const_iterator
        template<class O, std::enable_if_t<std::is_same_v<O, value_type> &&
                                               !std::is_same_v<O, Type>,
                                           int> = 0>
        iterator_impl(const iterator_impl<O>& rhs) :
            it(rhs.it), idx(rhs.idx), multi_maps(rhs.multi_maps) {}

        // these should probably be made into free friend functions
        bool operator==(const iterator_impl& rhs) const { return it == rhs.it; }
        bool operator!=(const iterator_impl& rhs) const { return it != rhs.it; }

        // I didn't modify operator++

        // I made operator->() return a pointer type directly:
        auto operator->() { return &*it; }
        const_pointer operator->() const { return &*it; }

        auto operator*() { return *it; }
        const_reference operator*() const { return *it; }

        // conditionally selecting the underlying iterator type:
        std::conditional_t<
            std::is_same_v<Type, const value_type>,
            MapConstIter, MapIter> it{};

        size_t idx{};
        MapType* multi_maps{};
    };

public:
    // The actual public iterator types a user will use:
    using iterator = iterator_impl<value_type>;
    using const_iterator = iterator_impl<const value_type>;

    // Added cbegin() and cend():
    const_iterator cbegin() const {
        // Since finding the beginning isn't a simple task, I've chosen to
        // reuse the non-const begin() + the conversion to const_iterator here.
        // This should simplify maintenance. Note the safe const_cast here:
        return const_cast<MultiMap*>(this)->begin();
    }

    const_iterator cend() const {
        // same as above for consistency
        return const_cast<MultiMap*>(this)->end();
    }

    // Now these just call cbegin() and cend():
    const_iterator begin() const { return cbegin(); }
    const_iterator end() const { return cend(); }

    iterator begin() {
        size_t idx = 0;
        auto it = multi_maps_[idx].begin();
        while(it == multi_maps_[idx].end() && idx + 1 < MAP_NUM) {
            it = multi_maps_[++idx].begin();
        }
        return {it, idx, multi_maps_};
    }

    iterator end() {
        return {multi_maps_[MAP_NUM - 1].end(), MAP_NUM - 1, multi_maps_};
    }

    // I didn't modify find(), insert(), clear() or compute_idx()

    size_t size() const {
        // Just en example of using a standard algoritm (from <numeric>):
        return std::accumulate(
            std::begin(multi_maps_), std::end(multi_maps_), size_t(0),
            [](size_t current, const auto& m) { return current + m.size(); });
    }

private:
    MapType multi_maps_[MAP_NUM];
    std::hash<K> hasher_{};
};

iteratorconst_iterator 的复制构造和转换现在可以工作,但是从const_iteratoriterator 的转换无法编译:

int main() {
    MultiMap<int, int>::iterator it1;
    MultiMap<int, int>::iterator it2 = it1;
    it1 = it2;
    MultiMap<int, int>::const_iterator cit1;
    MultiMap<int, int>::const_iterator cit2 = cit1;
    cit1 = cit2;

    cit2 = it1;
    // it1 = cit2; // error: no conversion from const_iterator to iterator
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-09-28
    • 2021-01-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-31
    • 2012-10-31
    相关资源
    最近更新 更多