问题描述
更新:
感谢所有提交答案的人。
简而言之,答案是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;
}
解决方法
是的,迭代器必须是可复制的。这是因为范围迭代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
和__end
是auto
,而不是auto &
或类似的东西。它们是非引用类型。用很多词来说,“ begin_expr”和“ end_expr”是begin
和end
表达式,它们最终调用您的自定义begin()
和end()
。
即使您的begin()
和end()
返回引用,它们也会被分配为非引用类型,因此必须是可复制的。
请注意,即使不是这种情况,所示的实现也不是很有用,因为两个引用始终都是相同的对象,因此begin和end表达式最终将是同一对象,并且始终比较(希望)相等。
,基于范围的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 ;
begin
和end
被复制。 Container::begin()
和Container::end()
均返回Iterator &
, begin-expr 和 end-expr 均为左值表达式,然后{{1} }必须是CopyConstructible。如果Iterator
和Container::begin()
返回Container::end()
,并且 begin-expr 和 end-expr 是xvalue表达式,则{{1} }必须是MoveConstructible。如果Iterator &&
和Iterator
返回Container::begin()
,并且 begin-expr 和 end-expr 是prvalue表达式,则{{1} }由于强制性的复制省略(自C ++ 17开始),因此不必是CopyConstructible或MoveConstructible的,但是请注意,迭代器被视为指针的抽象,它们通常应该是可复制的。
不可复制类型不是Iterator,因为Iterator的概念要求该类型满足CopyConstructible的概念。
也就是说,(除非我没有错过它),标准在技术上并不是基于范围的for循环实际使用Iterators。虽然您的示例确实尝试复制“迭代器”,因此无法正常工作,但进行较小的更改就可以解决:
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 };
}
};