只需一点介绍,用简单的话说。
在C ++中,迭代器是"事物"您可以在其上至少编写解除引用运算符*it
,增量运算符++it
,以及更高级的双向迭代器,减量--it
,以及最后但并非最不重要的随机访问迭代器我们需要运算符索引it[]
以及可能的加法和减法。
这样的"事物"在C ++中是具有相应运算符重载的类型的对象,或简单和简单的指针。
std::vector<>
是一个包装连续数组的容器类,因此指针作为迭代器是有意义的。在网络上,在一些文献中,您可以找到vector.begin()
用作指针。
使用指针的基本原理是开销更少,性能更高,特别是如果优化编译器检测到迭代并执行其操作(向量指令和内容)。使用迭代器可能更难以使编译器进行优化。
知道这一点,我的问题是为什么现代STL实现,让我们在Mingw 4.7中使用MSVC ++ 2013或libstdc ++,为矢量迭代器使用一个特殊的类?
答案 0 :(得分:21)
你完全正确vector::iterator
可以通过一个简单的指针实现(参见here) - 实际上迭代器的概念是基于指向数组的指针的概念元件。但是,对于其他容器,例如map
,list
或deque
,指针根本不起作用。那为什么不这样做呢?以下是为什么类实现比原始指针更可取的三个原因。
将迭代器实现为单独的类型允许附加功能(超出标准要求),例如(在编辑后添加添加注释)在解除引用时添加断言的可能性迭代器,例如,在调试模式下。
重载解析如果迭代器是指针T*
,它可以作为有效参数传递给采用T*
的函数,而这是不可能的使用迭代器类型。因此,使std::vector<>::iterator
指针实际上改变了现有代码的行为。例如,考虑一下
template<typename It>
void foo(It begin, It end);
void foo(const double*a, const double*b, size_t n=0);
std::vector<double> vec;
foo(vec.begin(), vec.end()); // which foo is called?
依赖于参数的查找(ADL;由juanchopanza指出)如果进行非限定调用,ADL将确保仅在参数为namespace std
时才会搜索namespace std
中的函数std::vector<double> vec;
sort(vec.begin(), vec.end()); // calls std::sort
sort(vec.data(), vec.data()+vec.size()); // fails to compile
中定义的类型。所以,
std::sort
如果vector<>::iterator
只是一个指针,则找不到 HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall\{05BD04E9-4AB5-46AC-891E-60EA8FD57D56}_is1
。
答案 1 :(得分:7)
迭代器的实现是实现定义,只要满足标准的要求即可。它可以是vector
的指针,它可以工作。不使用指针有几个原因;
如果所有迭代器都是指针,则++it
上的map
不会将其递增到下一个元素,因为内存不需要是非连续的。过去std:::vector
大多数标准容器的连续内存需要“更智能”的指针 - 因此迭代器。
迭代器的物理要求dove-tail非常好,逻辑要求元素之间的移动是一个定义好的“成语”迭代它们,而不仅仅是移动到下一个存储位置。
这是STL的原始设计要求和目标之一;容器之间的正交关系,算法以及通过迭代器将两者连接起来。
既然它们是类,您可以添加一大堆错误检查和健全性检查来调试代码(然后将其删除以获得更优化的发布代码)。
鉴于基于类的迭代器带来的积极方面,为什么应该或者不应该只使用指针std::vector
迭代器 - 一致性。 std::vector
的早期实现确实使用了普通指针,您可以将它们用于vector
。一旦你必须为其他迭代器使用类,给定它们带来的肯定,将它应用于vector
会成为一个好主意。
答案 2 :(得分:3)
使用指针的基本原理是开销越小越高 性能,特别是如果优化编译器检测到迭代 做它的事(矢量指令和东西)。使用迭代器 编译器可能更难以优化。
可能是,但它不是。如果你的实现并不完全,那么包装指针的结构将达到相同的速度。
考虑到这一点,很容易看到简单的好处,如更好的诊断消息(命名迭代器而不是T *),更好的重载分辨率,ADL和调试检查使得结构明显胜过指针。原始指针没有任何优势。
答案 3 :(得分:2)
因为STL的设计理念是你可以在迭代器上编写迭代的东西,无论迭代器是否等同于指向内存连续数组元素的指针(例如std::array
或std::vector
)或类似链接列表,一组键,在访问时即时生成的内容等。
另外,不要被愚弄:在向量的情况下,解除引用可能(没有调试选项)只是分解为一个无法使用的指针取消引用,因此编译后甚至不会有开销!
答案 4 :(得分:2)
使用指针的基本原理是开销越小越高 性能,特别是如果优化编译器检测到迭代 做它的事(矢量指令和东西)。使用迭代器 编译器可能更难以优化。
这是对问题核心的误解。一个格式良好的类实现将没有开销和相同的性能,因为编译器可以优化掉抽象并将迭代器类视为std::vector
的情况下的指针。
那就是说,
在Mingw 4.7中使用MSVC ++ 2013或libstdc ++,使用一个特殊的vector类 迭代器
因为他们认为添加一个抽象层class iterator
来定义std::vector
上的迭代概念比使用普通指针更有利。
抽象具有不同的成本与收益的集合,通常会增加设计复杂性(不一定与性能或开销相关),以换取灵活性,未来验证,隐藏实现细节。上述编译器认为,增加的复杂性是支付抽象效益的适当成本。
答案 5 :(得分:0)
通过解除引用并立即再次引用迭代器,我解决了这个令人讨厌的障碍。它看起来很荒谬,但它满足了MSVC ......
class Thing {
. . .
};
void handleThing(Thing* thing) {
// do stuff
}
vector<Thing> vec;
// put some elements into vec now
for (auto it = vec.begin(); it != vec.end(); ++it)
// handleThing(it); // this doesn't work, would have been elegant ..
handleThing(&*it); // this DOES work
答案 6 :(得分:0)
我认为原因很简单:最初std::vector
不需要在连续的内存块上实现。
因此,接口不能只显示一个指针。
来源:https://stackoverflow.com/a/849190/225186
此问题后来得到修复,并且std::vector
必须位于连续内存中,但是将std::vector<T>::iterator
用作指针可能为时已晚。
也许某些代码已经依赖iterator
成为 class/struct
。
有趣的是,我发现std::vector<T>::iterator
的实现是有效的,并生成了一个“空”迭代器(就像空指针一样)it = {};
。
std::vector<double>::iterator it = {};
assert( &*it == nullptr );
此外,在我所看到的实现中,std::array<T>::iterator
和std::initializer_list<T>::iterator
都是指针T*
。
从理论上讲,在我看来,将std::vector<T>::iterator
作为简单指针是完全可以的。
实际上,作为内建函数对于元编程具有明显的作用(例如std::vector<T>::iterator::difference_type
无效,是的,应该使用iterator_traits
)。
不是原始指针具有(非常)不允许(禁止)可空性(it == nullptr
)或(如果您愿意的话)默认电导率的边际优势。 (对于一般的编程观点,该参数无关紧要。)
同时,专用类迭代器在其他元编程方面的成本也很高,因为如果::iterator
是一个指针,则不需要使用临时方法来检测连续内存(请参阅{{1} https://en.cppreference.com/w/cpp/iterator/iterator_tags中的})和向量上的通用代码可以直接转发到旧版C函数。
仅出于这个原因,我会认为迭代器不是指针是一个代价高昂的错误。只是很难与C代码进行交互(因为您需要另一层功能和类型检测才能将内容安全地转发到C)。
话虽如此,我认为我们仍然可以通过允许从迭代器到指针的自动转换以及从指针到vector :: iterators的显式(?)转换来使事情变得更好。