我读到std::vector
应该是连续的。我的理解是,它的元素应该存储在一起,而不是散布在整个内存中。我只是接受了这一事实,并在例如使用其data()
方法获取底层连续内存时使用了该知识。
但是,我遇到了一种情况,矢量的记忆以一种奇怪的方式表现:
std::vector<int> numbers;
std::vector<int*> ptr_numbers;
for (int i = 0; i < 8; i++) {
numbers.push_back(i);
ptr_numbers.push_back(&numbers.back());
}
我希望这能给我一些数字的向量和指向这些数字的指针的向量。但是,在列出ptr_numbers
指针的内容时,会有不同的看似随机的数字,好像我正在访问错误的内存部分。
我试图检查每一步的内容:
for (int i = 0; i < 8; i++) {
numbers.push_back(i);
ptr_numbers.push_back(&numbers.back());
for (auto ptr_number : ptr_numbers)
std::cout << *ptr_number << std::endl;
std::cout << std::endl;
}
结果大致如下:
1
some random number
2
some random number
some random number
3
因此,似乎当我push_back()
到numbers
向量时,它的旧元素会更改其位置。
那么std::vector
是一个连续的容器到底是什么意思,为什么它的元素会移动?是否需要将它们存储在一起,但是在需要更多空间时将它们一起移动?
编辑:std::vector
仅在C ++ 17之后才是连续的吗? (只是保持对我以前的主张的评论与未来的读者相关。)
答案 0 :(得分:5)
那么std :: vector是一个连续的容器到底是什么意思,为什么它的元素会移动?是否需要将它们存储在一起,但是在需要更多空间时将它们一起移动?
这就是它的工作方式,为什么在发生重新分配时附加元素确实会使所有迭代器以及内存位置无效¹。这不仅从C ++ 17开始有效,从那以后一直如此。
这种方法有很多好处:
data()
方法可用于将基础原始内存传递给使用原始指针的API。push_back
,reserve
或resize
上分配新内存的成本归结为恒定时间,因为几何增长会随着时间的流逝而摊销(每次称为push_back
在libc ++和libstdc ++中,容量增加了一倍,在MSVC中大约增加了1.5倍。这些含义可以视为这种内存布局的缺点:
push_front
(提供std::list
或std::deque
之类的操作(insert(vec.begin(), element)
有效,但可能很昂贵¹),以及有效的合并/拼接多个向量实例。¹感谢@FrançoisAndrieux指出这一点。
答案 1 :(得分:1)
就实际结构而言,std::vector
在内存中看起来像这样:
struct vector { // Simple C struct as example (T is the type supplied by the template)
T *begin; // vector::begin() probably returns this value
T *end; // vector::end() probably returns this value
T *end_capacity; // First non-valid address
// Allocator state might be stored here (most allocators are stateless)
};
Relevant code snippet from the libc++
implementation as used by LLVM
打印std::vector
的原始内存内容:
(如果您不知道自己在做什么,请不要这样做!)
#include <iostream>
#include <vector>
struct vector {
int *begin;
int *end;
int *end_capacity;
};
int main() {
union vecunion {
std::vector<int> stdvec;
vector myvec;
~vecunion() { /* do nothing */ }
} vec = { std::vector<int>() };
union veciterator {
std::vector<int>::iterator stditer;
int *myiter;
~veciterator() { /* do nothing */ }
};
vec.stdvec.push_back(1); // Add something so we don't have an empty vector
std::cout
<< "vec.begin = " << vec.myvec.begin << "\n"
<< "vec.end = " << vec.myvec.end << "\n"
<< "vec.end_capacity = " << vec.myvec.end_capacity << "\n"
<< "vec's size = " << vec.myvec.end - vec.myvec.begin << "\n"
<< "vec's capacity = " << vec.myvec.end_capacity - vec.myvec.begin << "\n"
<< "vector::begin() = " << (veciterator { vec.stdvec.begin() }).myiter << "\n"
<< "vector::end() = " << (veciterator { vec.stdvec.end() }).myiter << "\n"
<< "vector::size() = " << vec.stdvec.size() << "\n"
<< "vector::capacity() = " << vec.stdvec.capacity() << "\n"
;
}
答案 2 :(得分:-1)
如果尝试以这种方式进行编码,则会发现值保持不变,并且向量中每个值的地址与其相邻元素的差值为4(有趣)。
std::vector<int> numbers;
std::vector<int*> ptr_numbers;
// adding values 0 up to 8 in the vector called numbers
for (int i = 0; i < 8; i++) {
numbers.push_back(i);
}
// printing out the values inside vector numbers
//and storing the address of each element of vector called numbers inside the ptr_numbers.
for (int i = 0; i != numbers.size(); i++) {
cout << numbers[i] << endl;
ptr_numbers.push_back(&numbers[i]);
}
cout << "" << endl;
// printing out the values of each element of vector ptr_numbers
for (int y = 0; y != ptr_numbers.size(); y++) {
cout << *ptr_numbers[y] << endl;
}
// printing out the address of each element of vector ptr_numbers
for (int y = 0; y != ptr_numbers.size(); y++) {
cout << &ptr_numbers[y] << endl;
}
当您遍历两个向量时。它们将输出相同的值。