代码之家  ›  专栏  ›  技术社区  ›  Adrian McCarthy

如何在实现常量和非常量迭代器时避免代码重复?

  •  45
  • Adrian McCarthy  · 技术社区  · 16 年前

    我正在使用类似STL的接口实现一个自定义容器。我必须提供一个正则迭代器和一个常量迭代器。两个版本的迭代器的大部分代码是相同的。如何避免这种重复?

    Foo ,我正在实施 FooIterator FooConstIterator operator++() 都是一样的。

    我的问题类似于 How do I remove code duplication between similar const and non-const member functions? 特别是对于非常量的方法,答案是非常量。我不认为这可以推广到迭代器问题。

    我应该有吗 食物迭代器 食物致畸器

    也许 应该包含一个 食物致畸器

    是否有聪明的模板技术从一个定义生成两个迭代器?或者,也许有一种方法——不寒而栗——使用预处理器来消除这些几乎相同的类。

    在以前的项目中,我的自定义容器构建在标准STL容器之上,因此我不必提供自己的迭代器。在这种情况下,这不是一个选项。

    5 回复  |  直到 9 年前
        1
  •  27
  •   Adrian McCarthy    9 年前

    [不幸的是,最好的答案被版主删除了,因为这是一个只有链接的答案。我理解为什么不鼓励只链接的答案;然而,删除它剥夺了未来搜索者非常有用的信息。该链接已保持稳定七年多,并在撰写本文时继续有效。]

    "The Standard Librarian: Defining Iterators and Const Iterators" ,2001年1月。如果这个链接坏了,现在Dobb博士的已经停止运行,它也可以使用 here .

    其思想是将迭代器作为一个模板实现一次,该模板接受一个额外的模板参数,一个布尔值,表示这是否是const版本。在实现中常量和非常量版本不同的任何地方,都可以使用模板机制来选择正确的代码。马特·奥斯特的机制被称为 choose . 看起来是这样的:

    template <bool flag, class IsTrue, class IsFalse>
    struct choose;
    
    template <class IsTrue, class IsFalse>
    struct choose<true, IsTrue, IsFalse> {
       typedef IsTrue type;
    };
    
    template <class IsTrue, class IsFalse>
    struct choose<false, IsTrue, IsFalse> {
       typedef IsFalse type;
    };
    

    如果对常量迭代器和非常量迭代器有单独的实现,那么常量实现将包括以下类型的typedef:

    typedef const T &reference;
    typedef const T *pointer;
    

    而非const实施将有:

    typedef T &reference;
    typedef T *pointer;
    

    但是 ,您可以有一个基于额外模板参数进行选择的实现:

    typedef typename choose<is_const, const T &, T &>::type reference;
    typedef typename choose<is_const, const T *, T *>::type pointer;
    

    通过对底层类型使用typedef,所有迭代器方法都可以具有相同的实现。看马特·奥斯特的 complete example .

        2
  •  11
  •   Hymir    8 年前

    constness.h:

    #ifndef ITERATOR_H
    #define ITERATOR_H
    #include <cstddef>
    #include <cstdint>
    #include <type_traits>
    #include <iterator>
    
    struct dummy_struct {
      int hello = 1;
      int world = 2;
      dummy_struct() : hello{ 0 }, world{ 1 }{ }
    };
    
    template< class T >
    class iterable {
      public:
        template< bool Const = false >
        class my_iterator {
          public:
            using iterator_category = std::forward_iterator_tag;
            using value_type = T;
            using difference_type = std::ptrdiff_t;
            /* deduce const qualifier from bool Const parameter */
            using reference = typename std::conditional_t< Const, T const &, T & >;
            using pointer = typename std::conditional_t< Const, T const *, T * >;
    
          protected:
            pointer i;
    
          public:
            my_iterator( T* _i ) : i{ reinterpret_cast< pointer >( _i ) } { }
    
            /* SFINAE enables the const dereference operator or the non 
               const variant
               depending on bool Const parameter */          
            template< bool _Const = Const >
            std::enable_if_t< _Const, reference >
            operator*() const {
              std::cout << "Const operator*: ";
              return *i;
            }
    
            template< bool _Const = Const >
            std::enable_if_t< !_Const, reference >
            operator*() {
              std::cout << "Non-Const operator*: ";
              return *i; 
            }
    
            my_iterator & operator++() {
              ++i;
              return *this;
            }
            bool operator!=( my_iterator const & _other ) const {
              return i != _other.i;
            }
    
            bool operator==( my_iterator const & _other ) const {
              return !( *this != _other );
            }   
        };  
    
    
    
      private:
        T* __begin;
        T* __end; 
      public:
        explicit iterable( T* _begin, std::size_t _count ): __begin{ _begin }, __end{ _begin + _count } { std::cout << "End: " << __end << "\n"; }
    
        auto begin()  const { return my_iterator< false >{ __begin }; }
        auto end()    const { return my_iterator< false >{ __end }; }
    
        auto cbegin() const { return my_iterator< true >{ __begin }; }
        auto cend()   const { return my_iterator< true >{ __end }; }
    };
    #endif
    

    这可以与以下内容一起使用:

    #include <iostream>
    #include <array>
    #include "constness.h"
    
    int main() {
    
      dummy_struct * data = new dummy_struct[ 5 ];
      for( int i = 0; i < 5; ++i ) {
        data[i].hello = i;
        data[i].world = i+1;
      } 
      iterable< dummy_struct > i( data, 5 );
    
      using iter = typename iterable< dummy_struct >::my_iterator< false >;
      using citer = typename iterable< dummy_struct >::my_iterator< true >;
    
      for( iter it = i.begin(); it != i.end(); ++it  ) {
        std::cout << "Hello: " << (*it).hello << "\n"
                  << "World: " << (*it).world << "\n";
      }
    
      for( citer it = i.cbegin(); it != i.cend(); ++it  ) {
        std::cout << "Hello: " << (*it).hello << "\n"
                  << "World: " << (*it).world << "\n";
      }
      delete[] data;
    
    }
    
        3
  •  2
  •   UncleBens    16 年前

    除了建议将常量和非常量模板化之外,还可以通过查看 Boost.Iterator tutorial -这也提到了同样的解决方案。

        4
  •  2
  •   Yola    10 年前

    template<class _Myvec>
        class _Vector_iterator
            : public _Vector_const_iterator<_Myvec>
    
        5
  •  2
  •   Unapiedra    6 年前

    阿瑟·奥德怀尔在他的博客文章中详细回答了这一问题: https://quuxplusone.github.io/blog/2018/12/01/const-iterator-antipatterns/

    template<bool IsConst>
    class MyIterator {
        int *d_;
    public:
        MyIterator(const MyIterator&) = default;  // REDUNDANT BUT GOOD STYLE
    
        template<bool IsConst_ = IsConst, class = std::enable_if_t<IsConst_>>
        MyIterator(const MyIterator<false>& rhs) : d_(rhs.d_) {}  // OK
    };
    using Iterator = MyIterator<false>;
    using ConstIterator = MyIterator<true>;
    };
    

    另外,添加 static_assert(std::is_trivially_copy_constructible_v<ConstIterator>); 对于代码,为了确保迭代器保持简单的可复制性:

    结论:如果您正在使用这种单向隐式转换行为实现自己的容器迭代器或任何其他类型对,例如联网TSs const_buffers_类型和可变_buffers_类型,那么您应该使用上述模式之一来实现转换构造函数 无需意外禁用琐碎的可复制性

        6
  •  1
  •   Roger Pate Roger Pate    16 年前

    您可以使用CRTP和一个公共基来“注入”方法(但您仍然必须在当前的C++中复制CTOR),或者只使用预处理器(无需抖动;可以轻松地处理以下问题:

    struct Container {
    
    #define G(This) \
    This operator++(int) { This copy (*this); ++*this; return copy; }
    // example of postfix++ delegating to ++prefix
    
      struct iterator : std::iterator<...> {
        iterator& operator++();
        G(iterator)
      };
      struct const_iterator : std::iterator<...> {
        const_iterator& operator++();
        G(const_iterator)
      };
    
    #undef G
    // G is "nicely" scoped and treated as an implementation detail
    };
    

    使用std::iterator、它提供的typedef以及您可能提供的任何其他typedef来直接生成宏。

    推荐文章