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

How to avoid code duplication implementing const and non-const iterators?

提问人:Adrian McCarthy 提问时间:1/28/2010 最后编辑:CommunityAdrian McCarthy 更新时间:10/31/2023 访问量:19068

问:

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

例如,我的容器类是 ,我正在实现 和 。两个迭代器都必须提供相同的方法。FooFooIteratorFooConstIteratoroperator++()

我的问题类似于如何删除相似的 const 和非 const 成员函数之间的代码重复?,但这个问题的答案特定于 const 和非 const 方法,尤其是访问器。我不明白这如何推广到迭代器问题。

我是否应该从其他非常量方法派生并扩展它?这要么导致虚拟方法,要么导致方法隐藏,这在这里似乎不合适。FooIteratorFooConstIterator

也许应该包含一个 .尽管这种方法确实减少了实现重复,但它似乎重新引入了许多样板方法定义。FooIteratorFooConstIterator

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

我尝试查看我的本地 STL 实现,看看它是如何处理这个问题的。有太多的帮助程序类,我无法摸索设计,但看起来功能只是重复的。

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

C++ 迭代器 常量 dry

评论


答:

2赞 UncleBens 1/28/2010 #1

除了建议你可以模板化恒量和非恒定性之外,你还可以通过查看 Boost.Iterator 教程来减少工作量 - 它也提到了相同的解决方案。

2赞 Roger Pate 1/28/2010 #2

您可以使用 CRTP 和公共基来“注入”方法(但您仍然必须在当前的 C++ 中复制 ctor),或者只使用预处理器(无需颤抖;轻松处理 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、它提供的 typedefs 以及您可能提供的任何其他 typedef 来使宏变得简单明了。

4赞 Yola 7/9/2015 #3

STL 使用继承

template<class _Myvec>
    class _Vector_iterator
        : public _Vector_const_iterator<_Myvec>
32赞 2 revs, 2 users 98%Adrian McCarthy #4

我强烈推荐 Matt Austern 在 Dobb 博士的期刊上发表的题为“The Standard Librarian: Defining Iterators and Const Iterators”的原文,2001 年 1 月。如果该链接坏了,现在 Dr. Dobb's 已经停止运营,也可以在这里找到。

为了防止这个替代答案被删除,我将总结解决方案。

这个想法是将迭代器实现一次作为模板,该模板采用额外的模板参数,一个布尔值,用于说明这是否是常量版本。在实现中,如果 con-st 版本和非 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;

而 non-con-st 实现将具有:

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;

通过对基础类型使用 typedefs,所有迭代器方法都可以具有相同的实现。请参阅 Matt Austern 的完整示例

评论

1赞 Leedehai 8/25/2017
但在 STL 中,迭代器类被定义为容器的成员类,因此 std::vector<int>::iterator 是有效的。Matt Austern 的代码将 slist_iterator 类定义为 slist 的外部类。
1赞 Adrian McCarthy 9/6/2017
@user8385554:我认为这个想法是 Matt Austern 的 slist 容器将具有 typedefs,并使迭代器可用,就好像它们是成员类型一样。iteratorconst_iterator
0赞 Adrian McCarthy 10/7/2019
@L.F.:这个问题是在 2010 年被问到的,当时 std::conditional_t 还没有出现。
1赞 Adrian McCarthy 10/9/2019
最初的答案也是 2010 年,但它是仅链接的,链接最终腐烂了。这个重新创建的答案是 2016 年,但保留了原始问题的上下文。的确,到 2016 年,这已经是一回事了,但当时还没有在所有主要的编译器中实现。std::conditional
0赞 BAKE ZQ 8/26/2020
有趣的是,最后一个链接已关闭。
13赞 Hymir 3/22/2018 #5

从 C++11/14 开始,您可以避免这种小的帮助程序,直接从布尔模板中推断出恒常性。

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赞 iBug 10/5/2018
在 C++ 11 中,您必须使用,因为帮助程序类型是在 C++14 中引入的。typename std::conditional<B, T, F>::typestd::conditional_t
2赞 Unapiedra 6/21/2019 #6

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

结论:如果您正在实现自己的容器迭代器,或者具有这种“单向隐式转换”行为的任何其他类型对,例如网络 TS 的const_buffers_type和mutable_buffers_type,那么您应该使用上述模式之一来实现转换构造函数,而不会意外地禁用简单的可复制性