繁体   English   中英

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

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

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

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

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

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

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

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

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

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

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

我强烈推荐Matt Austern 在2007年1月发表的题为“标准图书管理员:定义迭代器和常量迭代器”的原始Dr. Dobb's Journal文章。如果这个链接变坏,既然Dobb博士已经停止运行,它也可以在这里找到

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

我们的想法是将迭代器作为一个模板实现一次,该模板需要一个额外的模板参数,一个布尔值表示这是否是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的完整示例

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

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;

}

STL使用继承

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

除了你可能会将constness和non-constness模板化的建议之外,你还可以通过查看Boost.Iterator教程来减少工作量 - 这也提到了相同的解决方案。

你可以使用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来使宏直截了当。

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 - 那么您应该使用上述模式之一来实现转换构造函数不小心禁用琐碎的可复制性

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM