簡體   English   中英

在基於 C++ 范圍的 for 循環中,begin() 可以返回對不可復制迭代器的引用嗎?

[英]In a C++ range-based for loop, can begin() return a reference to a non-copyable iterator?

更新:

感謝所有提交答案的人。

簡而言之,答案是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&)'

根據錯誤,我得出的結論是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;
}

是的,迭代器必須是可復制的。 這是因為范圍迭代在邏輯上等同於以下代碼

    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 &或類似的東西。 它們是非引用類型。 “begin_expr”和“end_expr”,用很多的話來說,是beginend表達式,最終調用您的自定義begin()end()

即使您的begin()end()返回引用,它們也會被分配給非引用類型,因此必須是可復制的。

請注意,即使情況並非如此,所顯示的實現也不是很有用,因為兩個引用始終是同一個對象,因此開始和結束表達式最終將是同一個對象,並且總是比較相等(希望)。

[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 &並且begin-exprend-expr都是左值表達式,那么Iterator必須是可復制構造的。 如果Container::begin()Container::end()返回Iterator &&並且begin-exprend-expr是 xvalue-expressions,則Iterator需要是 MoveConstructible。 如果Container::begin()Container::end()返回Iterator並且begin-exprend-expr是純右值表達式,則Iterator不需要是 CopyConstructible 或 MoveConstructible 因為強制復制省略(因為 C++ 17),但請注意,迭代器被視為指針的抽象,它們通常應該是可復制的。

不可復制的類型不是迭代器,因為迭代器的概念要求類型滿足 CopyConstructible 的概念。

也就是說,(除非我錯過了),標准在技術上並不是基於范圍的 for 循環來實際使用迭代器。 雖然您的示例確實嘗試復制“迭代器”,因此無法正常工作,但只需稍作更改即可修復:

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

這在 C++17 之前不起作用,因為會從臨時對象移動。 因此,需要進行另一項更改,使類型可移動。

在基於 C++ 范圍的 for 循環中,begin() 可以返回對不可復制迭代器的引用嗎?

不,但它可以向這樣的非迭代器返回一個值。

你總是可以創建一個類來存儲你的引用,它的復制構造函數只是復制引用:

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

};

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM