C++ std::vector<>::迭代器不是指针,为什么?
C++ std::vector<>::iterator is not a pointer, why?
只是一个简单的介绍。在c++中,迭代器是一种"东西",你至少可以在上面写解引用操作符*it
,自增操作符++it
,对于更高级的双向迭代器,可以写自减操作符--it
,最后但并非最不重要的是,对于随机访问迭代器,我们需要操作符索引it[]
,可能还需要加法和减法。
c++中这样的"东西"是具有根据操作符重载类型的对象,或普通指针和简单指针。
std::vector<>
是包装连续数组的容器类,因此指针作为迭代器是有意义的。在网上,在一些文献中,你可以发现vector.begin()
被用作指针。
使用指针的基本原理是更少的开销,更高的性能,特别是如果优化编译器检测到迭代并做它的事情(向量指令和东西)。使用迭代器可能会使编译器更难优化。
知道了这一点,我的问题是为什么现代STL实现,比如msvc++ 2013或libstdc++在Mingw 4.7,使用一个特殊的类向量迭代器?
你完全正确,vector::iterator
可以通过一个简单的指针来实现(见这里)——实际上,迭代器的概念是基于指向数组元素的指针的概念。然而,对于其他容器,如map
、list
或deque
,指针根本不起作用。那么,为什么没有这样做呢?下面是类实现优于原始指针的三个原因:
-
将迭代器实现为单独的类型允许额外的功能(超出标准所要求的),例如(在quentin注释之后的编辑中添加)在解引用迭代器时添加断言的可能性,例如,在调试模式下。
-
重载解析如果迭代器是指针
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;如果进行非限定调用,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
如果
vector<>::iterator
仅仅是一个指针,则无法找到std::sort
。迭代器的实现是实现定义的,只要满足标准的要求。它可以是vector
的指针,这将工作。不使用指针有以下几个原因:
- 与其他容器的一致性。
- 调试和错误检查支持
- 重载解析,基于类的迭代器允许将重载与普通指针区分开来。
如果所有迭代器都是指针,则map
上的++it
不会将其自增到下一个元素,因为不要求内存是非连续的。超过std:::vector
的连续内存,大多数标准容器需要"更智能"的指针——因此需要迭代器。
迭代器的物理要求与逻辑要求非常吻合,即元素之间的移动必须有一个定义良好的"习惯用法",即遍历元素,而不仅仅是移动到下一个内存位置。
这是STL最初的设计要求和目标之一;容器、算法之间的正交关系,以及通过迭代器将两者连接。
既然它们是类,你可以添加一大堆错误检查和健全检查来调试代码(然后删除它以获得更优化的发布代码)。
考虑到基于类的迭代器带来的积极方面,为什么应该或不应该在std::vector
迭代器中只使用指针——一致性?std::vector
的早期实现确实使用了普通指针,您可以将它们用于vector
。一旦您必须为其他迭代器使用类,考虑到它们带来的积极影响,将其应用于vector
将成为一个好主意。
使用指针的基本原理是更少的开销,更高性能,特别是当优化编译器检测到迭代时然后做它的事情(矢量指令之类的)。使用迭代器可能会使编译器更难优化。
它可能是,但它不是。如果你的实现不是完全正确的,一个包含指针的结构体将达到相同的速度。
考虑到这一点,很容易看到一些简单的好处,如更好的诊断消息(将迭代器命名为T*)、更好的重载解析、ADL和调试检查,使结构体明显优于指针。原始指针没有任何优势。使用指针的基本原理是更少的开销,更高性能,特别是当优化编译器检测到迭代时然后做它的事情(矢量指令之类的)。使用迭代器可能会使编译器更难优化。
这是问题核心的误解。一个结构良好的类实现将没有开销,并且具有相同的性能,这一切都是因为编译器可以优化抽象,并将迭代器类仅作为std::vector
的指针。
话虽如此,
msvc++ 2013或libstdc++在mingw4.7,使用一个特殊的类向量迭代器
,因为他们认为添加一个抽象层class iterator
来定义在std::vector
上迭代的概念比使用普通指针更有益。
因为STL的设计思想是,你可以写一些在一个迭代器上迭代的东西,不管这个迭代器只是相当于一个指向内存连续数组(如std::array
或std::vector
)元素的指针,还是像链表、键集这样的东西,在访问时动态生成的东西,等等
同样,不要被愚弄了:在vector的情况下,解引用可能(没有调试选项)会分解成一个内联指针解引用,所以编译后甚至不会有开销!
我认为原因很简单:最初std::vector
不需要在连续的内存块上实现。所以接口不能只显示一个指针。
来源:https://stackoverflow.com/a/849190/225186
这个问题后来被修复了,std::vector
被要求在连续内存中,但是把std::vector<T>::iterator
变成一个指针可能太晚了。也许有些代码已经依赖于iterator
为 class/struct
。
有趣的是,我发现std::vector<T>::iterator
的实现中这是有效的,并生成了一个"null"迭代器(就像空指针)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
是一个指针,就不需要有专门的方法来检测连续内存(参见https://en.cppreference.com/w/cpp/iterator/iterator_tags中的contiguous_iterator_tag
),并且向量上的泛型代码可以直接转发给遗留的c函数。仅凭这个原因,我就认为迭代器不是指针是一个代价高昂的错误。它只是使与C代码交互变得困难(因为你需要另一层函数和类型检测来安全地将内容转发给C)。
话虽如此,我认为我们仍然可以通过允许从迭代器到指针的自动转换,或者从指针到vector::迭代器的显式(?)转换,来使事情变得更好。
我通过解引用并立即再次引用迭代器来绕过这个讨厌的障碍。这看起来很荒谬,但它满足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
- 为什么使用 "this" 指针调用派生成员函数?
- 为什么++(*p)更改指针值
- C++取消引用指针.为什么会发生变化
- 使用取消引用的指针的多态性会产生意外的结果.为什么?
- 为什么指针不写入类的地址?
- 为什么我们要为avl树实现返回一个指向节点的指针,而不是void函数
- 非类型指针和引用模板参数,以及在编译时如何/为什么解析它们.c++
- 为什么要增加导致崩溃的指针
- 为什么C++对链表中的下一个节点使用指针,而像 C# 或 Java 这样的语言只使用类 Node 的名称?
- 为什么会出现 gettnig 运行时错误:加载类型为"_Bit_type"(stl_bvector.h) 的空指针?
- 为什么 vector 的随机访问迭代器给出与指针不同的内存地址?
- 共享指针:为什么没有双免费
- 指向2D数组的指针(为什么起作用)
- 函数指针 - 为什么,什么时候我可以没有
- C++ 函数指针 -- 为什么会这样
- 不支持在c或c++中添加两个指针.为什么?
- 无效的指针-为什么
- 模板基类可以存储指向派生但不能存储对象的指针 - 为什么
- 在信号代码中将整数转换为函数指针-为什么这样做?
- 指向结构体作为函数参数的指针-为什么不更新其值