为简单起见,我会坚持vector<int>
,但我认为这适用于任何vector<T>
个对象。
如果我使用vector<int>::iterator
跟踪int的向量中的某个位置然后使用vector<int>::push_back()
,则迭代器变得毫无价值。意思是,我不能用&
取其地址或取消引用它。一旦我按照以下意义打印了某些对象的地址,直接原因就有了意义:
vector<int> my_vec(1); //my_vec[0] = 0
vector<int>::iterator it = my_vec.begin(); //it -> my_vec[0], *it = my_vec[0] = 0
cout << "&my_vec = " << &my_vec << "\n";
cout << "&my_vec[0] = " << &my_vec[0] << "\n";
cout << "&it = " << &it << "\n"; //prints address, all good
cout << "*it = " << *it << "\n"; //prints 0, all good
cout << "\n\n" << pushing back..." << "\n\n";
my_vec.push_back(1);
cout << "&my_vec = " << &my_vec << "\n"; //same as before push_back()!
cout << "&my_vec[0] = " << &my_vec[0] << "\n"; //different from before push_back()!!
cout << "&it = " << &it << "\n"; //same as before push_back()
//cannot do &it or *it
显然it
的地址没有变化,但push_back()
已经在内存中移动了一些东西,现在my_vec
的不同“元素”的地址也发生了变化。 my_vec [i]有一个新地址的事实对我有意义,但后来我有以下问题:
1)为什么my_vec
的地址没有变化?似乎如果push_back()
导致my_vec[i]
的地址发生变化,它也应该更改整个对象的地址。对于数组,my_array
是指向my_array[0]
的指针,因此我可以想象一个操作会更改每个my_array[i]
的地址并更新指针以指向my_array[0]
的新地址但指针my_array
作为对象的地址本身不会改变。但是my_vec
在任何意义上都不是指向my_vec[0]
的指针,所以我很困惑为什么my_vec[i]
的地址会改变,而不是对象my_vec
。
2)为什么更改vector<int>
(例如my_vec[i]
)地址的push_back()
内部任何操作都不能正确“更新”任何迭代器?这似乎是一个好主意?否?
3)鉴于#2就是它,当我调用push_back()
时,我的迭代器变得毫无价值,处理这个问题的正确方法是什么?如果我需要使用push_back()
,我是否应该使用迭代器?如果有人要抱怨我的用例是使用迭代器和push_back()
,我为了简洁而排除它,但它基本上是使用vector<int>
实现堆栈而我使用迭代器来跟踪堆栈的顶部。由于我不希望启动固定大小,因此当迭代器点击push_back()
时,我尝试使用my_vec.end()
来扩大堆栈。但我认为这是一个有效的问题。
非常感谢你的帮助!
答案 0 :(得分:7)
为什么
my_vec
的地址没有变化?
因为矢量对象本身仍然是同一地址的同一个对象。重新分配会更改它管理的动态数组的地址,而不是管理数组的矢量对象。
为什么更改
vector<int>
地址的my_vec[i]
内部的任何操作(例如push_back()
)也不能正确“更新”任何迭代器?这似乎是一个好主意?否?
那将有(可能很大)运行时成本。向量必须跟踪所有迭代器(需要动态存储,每次创建迭代器时进行内存分配,并在向量更改时更新所有向量)或每个迭代器需要对容器的引用,在每次访问时检查,并且无法实现为简单的指针。 C ++通常会在可能的情况下避免运行时成本 - 特别是在这种情况下,它们几乎总是不必要的。
处理这个问题的正确方法是什么?
有各种选择。您可以存储索引而不是迭代器。您可以使用诸如std::list
之类的容器和稳定的迭代器(尽管效率可能相当低)。如果可以对数组的大小设置上限,则可以保留该数量,以便不需要重新分配。您可以编写自己的容器(或适配器)来自动更新迭代器。对于你的堆栈,如果它确实是一个堆栈,你不需要跟踪除向量末尾之外的任何东西,所以根本不需要存储迭代器。或者你可以使用std::stack
而不是重新发明它。
是否有其他
vector<T>
成员函数(除了显而易见的成员函数)对迭代器有影响?
当任何操作导致向量增长超过其当前容量时,迭代器会因重新分配而失效。您可以使用reserve
功能控制容量。
此外,擦除元素将使任何迭代器无效,这些迭代器引用已删除的元素或序列中的元素。
答案 1 :(得分:1)
为什么my_vec的地址不会改变?
std::vector
中的内存重新分配发生在其内部缓冲区中,该内部缓冲区保存其元素而不是对象本身(即,不是std::vector
本身)。请考虑以下玩具示例:
template<typename T>
class vector {
T *buffer;
...
public:
...
};
如果我定义vector
对象(例如vector<int> v
),则对象v
会有一个地址。当由于插入或擦除而发生重新分配时,v
的地址不会发生变化,而是其成员变量buffer
的值(即buffer
将指向新地址位置)。
为什么更改my_vec [i]地址的向量内部的任何操作(例如push_back())也不能正确“更新”任何迭代器?
iterator invalidation的问题众所周知,当你必须处理这种情况时,你应该采取预防措施。
是否有任何其他向量成员函数(除了显而易见的函数)对迭代器有影响?它取决于T?
是的(例如,std::vector::insert
,std::vector::erase
)。不,它不依赖于T
(即std::vector
元素的类型)。