为自定义类型启用基于范围的for循环

正如我们在前面的系列文章中所看到的那样,基于范围的for循环(在其他编程语言中也被称为for each循环)允许您迭代范围内的所有元素,从而为标准循环提供简化的语法,并使代码在许多情况下更易读。但是,基于范围的for循环不能在任何表示范围的类型上直接使用,而需要作为成员或自由函数存在begin()和end()函数(对于非数组类型)。 在本系列文章中,我们将看到如何启用自定义类型以在基于范围的for循环中使用。

做好准备

如果需要了解基于范围的for循环是如何工作的,以及编译器为该循环生成的代码是什么,则建议您阅读该系列文章,然后使用基于范围的for循环在范围内进行迭代。

为了说明如何为表示序列的自定义类型启用基于范围的for循环,我们将使用以下简单数组的实现:

template <typename T, size_t const Size> 
class dummy_array 
{ 
  T data[Size] = {}; 

public: 
  T const & GetAt(size_t const index) const 
  { 
    if (index < Size) return data[index]; 
    throw std::out_of_range("index out of range"); 
  } 

  void SetAt(size_t const index, T const & value) 
  { 
    if (index < Size) data[index] = value; 
    else throw std::out_of_range("index out of range"); 
  } 

  size_t GetSize() const { return Size; } 
};

此系列文章的目的是使您可以编写如下代码:

dummy_array<int, 3> arr; 
arr.SetAt(0, 1); 
arr.SetAt(1, 2); 
arr.SetAt(2, 3); 

for(auto&& e : arr) 
{  
  std::cout << e << std::endl; 
}

怎么做……

要使自定义类型可以在基于范围的for循环中使用,您需要执行以下操作:

  • 为必须实现以下运算符的类型创建可变和常量迭代器:
    • operator++ 用于递增迭代。
    • operator* 用于解引用迭代器并访问迭代器指向的实际元素。
    • operator!=用于与另一个不等式迭代器进行比较。
  • 为该类型提供一定能够访问的begin()和end()函数。

给定早期的简单范围的示例,我们需要提供以下内容:

  1. 以下为迭代器类的最小实现:
template <typename T, typename C, size_t const Size> 
class dummy_array_iterator_type 
{ 
public: 
  dummy_array_iterator_type(C& collection,  
                            size_t const index) : 
  index(index), collection(collection) 
  { } 

bool operator!= (dummy_array_iterator_type const & other) const 
{ 
  return index != other.index; 
} 

T const & operator* () const 
{ 
  return collection.GetAt(index); 
} 

dummy_array_iterator_type const & operator++ () 
{ 
  ++index; 
  return *this; 
} 

private: 
  size_t   index; 
  C&       collection; 
};
  1. 可变和常量迭代器的别名模板:
template <typename T, size_t const Size> 
using dummy_array_iterator =  
   dummy_array_iterator_type< 
     T, dummy_array<T, Size>, Size>; 

template <typename T, size_t const Size> 
using dummy_array_const_iterator =  
   dummy_array_iterator_type< 
     T, dummy_array<T, Size> const, Size>;
  1. 自由的begin()和end()函数可返回相应的begin和end迭代器,两个别名模板均具有重载:
template <typename T, size_t const Size> 
inline dummy_array_iterator<T, Size> begin(
  dummy_array<T, Size>& collection) 
{ 
  return dummy_array_iterator<T, Size>(collection, 0); 
} 

template <typename T, size_t const Size> 
inline dummy_array_iterator<T, Size> end(
  dummy_array<T, Size>& collection) 
{ 
  return dummy_array_iterator<T, Size>(
    collection, collection.GetSize()); 
} 

template <typename T, size_t const Size> 
inline dummy_array_const_iterator<T, Size> begin( 
  dummy_array<T, Size> const & collection) 
{ 
  return dummy_array_const_iterator<T, Size>( 
    collection, 0); 
} 

template <typename T, size_t const Size> 
inline dummy_array_const_iterator<T, Size> end( 
  dummy_array<T, Size> const & collection) 
{ 
  return dummy_array_const_iterator<T, Size>( 
    collection, collection.GetSize()); 
}

这个如何起作用……

使用此实现后,前面显示的基于范围的for循环将按预期编译并执行。在执行依赖于参数的查找时,编译器将识别我们编写的两个begin()和end()函数(它们引用了dummy_array),因此生成的代码将变为有效。

在前面的示例中,我们定义了一个迭代器类模板和两个别名模板,分别称为dummy_array_iterator和dummy_array_const_iterator。 对于这两种类型的迭代器,begin()和end()函数都具有两个重载。这是必要的,以便我们考虑的容器可以在基于范围的常量和非常量实例的循环中使用:

template <typename T, const size_t Size> 
void print_dummy_array(dummy_array<T, Size> const & arr) 
{ 
  for (auto && e : arr) 
  { 
    std::cout << e << std::endl; 
  } 
}

为本系列文章中涉及的简单范围类为基于范围的for循环启用一种可能的替代方法是提供成员begin()和end()函数。通常,只有拥有并可以修改源代码,这才有意义。另一方面,此配方中显示的解决方案在所有情况下均适用,并且应优先于其他替代方案。

参见

  • 创建类型别名和别名模板

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据