【问题标题】:In a C++ range-based for loop, can begin() return a reference to a non-copyable iterator?在基于 C++ 范围的 for 循环中,begin() 可以返回对不可复制迭代器的引用吗?
【发布时间】:2020-08-30 02:40:14
【问题描述】:

更新:

感谢所有提交答案的人。

简而言之,答案是begin()end() 返回的“迭代器”必须是可复制的。

Artyer 提出了一个很好的解决方法:创建一个包含对不可复制对象的引用(或指针)的迭代器类。下面是示例代码:

struct  Element  {};

struct  Container  {

  Element  element;

  struct  Iterator  {
    Container *  c;
    Iterator  ( Container * c )  :  c(c)  {}
    bool  operator !=  ( const Iterator & end )  const  { return  c != end.c; }
    void  operator ++  ()  {  c  =  nullptr;  }
    const Element &  operator *  ()  const  {  return  c->element;  }
  };

  Iterator  begin  ()  {  return  Iterator ( this    );  }
  Iterator  end    ()  {  return  Iterator ( nullptr );  }

};

#include  <stdio.h>
int  main  ()  {
  Container  c;
  printf ( "main  %p\n", & c .element );
  for  (  const Element & e  :  c  )  {  printf ( "loop  %p\n", & e );  }
  return  0;
}

原问题:

下面的 C++ 代码将无法编译(至少在 Ubuntu 20.04 上使用 g++ 9.3.0 版时不会编译)。

错误信息是:
use of deleted function 'Iterator::Iterator(const Iterator&amp;)'

基于错误,我是否正确地得出结论begin()end() 返回的“迭代器”必须是可复制的?或者有什么方法可以使用通过引用返回的不可复制的迭代器?

struct  Iterator  {

  Iterator  ()  {}
  //  I want to prevent the copying of Iterators, so...
  Iterator  ( const Iterator & other )  =  delete;

  bool        operator !=  ( const Iterator & other )  {  return  false;   }
  Iterator &  operator ++  ()  {  return  * this;  }
  Iterator &  operator *   ()  {  return  * this;  }

};

struct  Container  {

  Iterator  iterator;

  Iterator &  begin()  {  return  iterator;  }
  Iterator &  end()    {  return  iterator;  }

};

int  main  ()  {
  Container  container;
  for  (  const Iterator & iterator  :  container  )  {}
  //  The above for loop causes the following compile time error:               
  //  error: use of deleted function 'Iterator::Iterator(const Iterator&)'      
  return  0;
}

【问题讨论】:

  • 基于范围的 for 假设它所操作的容器提供了可复制构造和可复制分配的迭代器。您的使用 for ( const Iterator &amp; iterator : container ) {} 也是错误的 - 它会依赖于 begin()end() 都产生迭代器,当取消引用时,可以给出 const Iterator &amp;
  • 不可复制类型不是迭代器,因为迭代器的概念要求类型满足 CopyConstructible 的概念。

标签: c++ iterator copy-constructor range-based-loop


【解决方案1】:

是的,迭代器必须是可复制的。这是因为范围迭代is logically equivalent to the following code:

    auto && __range = range_expression ;
    for (auto __begin = begin_expr, __end = end_expr; __begin != __end; ++__begin) {

        range_declaration = *__begin;
        loop_statement

    }

这是 C++11 版本。 C++17 及更高版本略有不同,但根本原因是相同的:__begin__endauto,而不是 auto &amp;,或类似的东西。它们是非引用类型。简而言之,“begin_expr”和“end_expr”是beginend 表达式,它们最终会调用您的自定义begin()end()

即使您的 begin()end() 返回引用,它们也会被分配给非引用类型,因此必须是可复制的。

请注意,即使不是这种情况,显示的实现也不是很有用,因为两个引用将始终是同一个对象,因此 begin 和 end 表达式最终将是同一个对象,并且总是比较相等(希望如此)。

【讨论】:

  • 问题中的示例代码并非有用。它的目的是简单的。干杯!
【解决方案2】:

[stmt.ranged]/1

基于范围的 for 语句

for ( init-statement(opt) for-range-declaration : for-range-initializer ) statement

等价于

{
  init-statement(opt)

  auto &&range = for-range-initializer ;
  auto begin = begin-expr ;
  auto end = end-expr ;
  for ( ; begin != end; ++begin ) {
      for-range-declaration = * begin ;
      statement
  }
}

注意在语句中

auto begin = begin-expr ;
auto end = end-expr ;

beginend 被复制。 Container::begin()Container::end() 返回 Iterator &amp; 并且 begin-exprend-expr 都是左值表达式,那么 Iterator 必须是 CopyConstructible。如果Container::begin()Container::end() 返回Iterator &amp;&amp; 并且begin-exprend-expr 是xvalue 表达式,那么Iterator 需要是MoveConstructible。如果 Container::begin()Container::end() 返回 Iterator 并且 begin-exprend-expr 是纯右值表达式,那么 Iterator 不需要是 CopyConstructible或 MoveConstructible 因为强制复制省略(自 C++17 起),但请注意,迭代器被视为指针的抽象,它们通常应该是可复制的。

【讨论】:

    【解决方案3】:

    不可复制的类型不是迭代器,因为迭代器的概念要求类型满足 CopyConstructible 的概念。

    也就是说,(除非我错过了),标准在技术上并不是基于范围的 for 循环来实际使用迭代器。虽然您的示例确实尝试复制“迭代器”因此无法工作,但只需稍作改动即可修复:

    struct  Container  {
        Iterator  begin()  {  return  {};  } // return by prvalue
        Iterator  end()    {  return  {};  }
    };
    

    这在 C++17 之前不起作用,因为会从临时对象移动。因此,需要进行另一项更改,即使类型可移动。

    在基于 C++ 范围的 for 循环中,begin() 是否可以返回对不可复制迭代器的引用?

    不,但它可以将值返回给这样的非迭代器。

    【讨论】:

      【解决方案4】:

      你总是可以创建一个类来存储你的引用,它的复制构造函数只是复制引用:

      template<class T>
      struct RangeForRef : private std::reference_wrapper<T> {
      
          using std::reference_wrapper<T>::reference_wrapper;
      
          bool operator!=(const RangeForRef& other) {
              return this->get() != other.get();
          }
          void operator++() {
              ++(this->get());
          }
          decltype(auto) operator*() {
              return *(this->get());
          }
      
      };
      
      struct  Container  {
      
        Iterator  iterator;
      
        RangeForRef<Iterator>  begin()  {  return iterator;  }
        RangeForRef<Iterator>  end()    {  return iterator;  }
      
      };
      
      

      虽然我怀疑您的迭代器类首先应该只包含对不可复制内容的引用,例如:

      struct Container {
      
          NonCopyable data;
      
          Iterator begin() {
              // Iterator has a `NonCopyable*` member
              // And copy assign / construct copies the pointer
              return { data };
          }
          Iterator end() {
              return { data };
          }
      
      };
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-11-10
        • 2012-10-16
        • 1970-01-01
        • 2022-01-14
        • 2020-07-19
        • 2012-08-25
        • 1970-01-01
        相关资源
        最近更新 更多