提问人:Adrian McCarthy 提问时间:1/28/2010 最后编辑:CommunityAdrian McCarthy 更新时间:10/31/2023 访问量:19068
如何避免实现 const 和非 const 迭代器的代码重复?
How to avoid code duplication implementing const and non-const iterators?
问:
我正在实现一个具有类似 STL 接口的自定义容器。我必须提供一个常规迭代器和一个常量迭代器。两个版本的迭代器的大多数代码是相同的。如何避免这种重复?
例如,我的容器类是 ,我正在实现 和 。两个迭代器都必须提供相同的方法。Foo
FooIterator
FooConstIterator
operator++()
我的问题类似于如何删除相似的 const 和非 const 成员函数之间的代码重复?,但这个问题的答案特定于 const 和非 const 方法,尤其是访问器。我不明白这如何推广到迭代器问题。
我是否应该从其他非常量方法派生并扩展它?这要么导致虚拟方法,要么导致方法隐藏,这在这里似乎不合适。FooIterator
FooConstIterator
也许应该包含一个 .尽管这种方法确实减少了实现重复,但它似乎重新引入了许多样板方法定义。FooIterator
FooConstIterator
是否有巧妙的模板技术可以从单个定义生成两个迭代器?或者,也许有一种方法可以 - 不寒而栗 - 使用预处理器来消除这些几乎相同的类。
我尝试查看我的本地 STL 实现,看看它是如何处理这个问题的。有太多的帮助程序类,我无法摸索设计,但看起来功能只是重复的。
在以前的项目中,我的自定义容器是建立在标准 STL 容器之上的,因此我不必提供自己的迭代器。在这种情况下,这不是一个选项。
答:
除了建议你可以模板化恒量和非恒定性之外,你还可以通过查看 Boost.Iterator 教程来减少工作量 - 它也提到了相同的解决方案。
您可以使用 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 来使宏变得简单明了。
STL 使用继承
template<class _Myvec>
class _Vector_iterator
: public _Vector_const_iterator<_Myvec>
我强烈推荐 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 的完整示例。
评论
iterator
const_iterator
std::conditional
从 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;
}
评论
typename std::conditional<B, T, F>::type
std::conditional_t
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,那么您应该使用上述模式之一来实现转换构造函数,而不会意外地禁用简单的可复制性。
上一个:为什么 C++ 不允许匿名结构?
下一个:如何截屏UAC提示?
评论