如何避免代码重复实现const和非const迭代器?

时间:2010-01-27 20:57:39

标签: c++ iterator const dry

我正在实现一个具有类似STL接口的自定义容器。我必须提供一个常规迭代器和一个const迭代器。两个版本的迭代器的大多数代码都是相同的。我怎样才能避免这种重复?

例如,我的容器类是Foo,我正在实现FooIteratorFooConstIterator。两个迭代器都必须提供类似operator++()的方法。

我的问题类似于How do I remove code duplication between similar const and non-const member functions?,但是那个问题的答案特定于const和非const方法,尤其是访问器。我没有看到这可能会如何推广到迭代器问题。

我应该从FooIterator派生FooConstIterator并使用其他非const方法扩展它吗?这要么导致虚拟方法或方法隐藏,这在这里似乎不合适。

或许FooIterator应包含FooConstIterator。虽然这种方法确实减少了实现重复,但它似乎重新引入了许多样板方法定义。

是否有巧妙的模板技术从单个定义生成两个迭代器?或许有一种方法 - 颤抖 - 使用预处理器来消除这些几乎相同的类。

我已经尝试查看我的本地STL实现,看看它是如何处理它的。有很多辅助类,我在设计方面遇到了麻烦,但看起来功能很简单。

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

6 个答案:

答案 0 :(得分:19)

[不幸的是,最好的答案是由主持人删除的,因为这是一个仅限链接的答案。我理解为什么不鼓励仅限链接的答案;但是,删除它会抢走未来寻求者非常有用的信息。该链接已保持稳定超过七年,并在撰写本文时继续有效。]

我强烈推荐Matt Austern在2001年1月发表的题为"The Standard Librarian: Defining Iterators and Const Iterators"的原始Dr. Dobb's Journal。如果Dobb博士已经停止运作,那么这个链接是否会变坏,它也可用here

为了防止删除此替换答案,我将总结解决方案。

这个想法是将迭代器作为一个模板实现一次,该模板带有一个额外的模板参数,一个布尔值表示这是否是const版本。在const和非const版本不同的实现中的任何地方,您都使用模板机制来选择正确的代码。 Matt Austern的机制被称为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;
};

如果你有const和非const迭代器的单独实现,那么const实现将包括这样的typedef:

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

并且非const实现将具有:

typedef T &reference;
typedef T *pointer;

但是使用choose,您可以拥有一个基于额外模板参数选择的实现:

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

通过对基础类型使用typedef,所有迭代器方法都可以具有相同的实现。见Matt Austern的complete example

答案 1 :(得分:8)

从C ++ 11/14开始,你可以避免这样的小助手直接从布尔模板中推导出const。

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;

}

答案 2 :(得分:3)

STL使用继承

template<class _Myvec>
    class _Vector_iterator
        : public _Vector_const_iterator<_Myvec>

答案 3 :(得分:2)

除了你可能会对常量和非常量进行模板化的建议之外,你还可以通过查看Boost.Iterator tutorial来减少工作量 - 这也提到了相同的解决方案。

答案 4 :(得分:1)

你可以使用CRTP和一个公共基础来“注入”方法(但你仍然需要在当前的C ++中复制ctors),或者只使用预处理器(不需要打扰;轻松处理ctors):

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来使宏直接。

答案 5 :(得分:0)

Arthor O'Dwyer在他的博客文章中详细回答了这个问题: 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>);,以确保迭代器保持可复制的构造性:

  

结论:如果要实现自己的容器迭代器-或任何其他具有这种“单向隐式转换”行为的类型,例如Networking TS的const_buffers_type和mutable_buffers_type,则应使用上述模式之一来实现实现转换构造函数,而不会意外禁用琐碎的可复制性