我正在为正确的思维模式和对std::vector
的理解而苦苦挣扎。
当创建类型T的向量,然后为该向量保留N个元素时,编译器基本上会找到并保留N * sizeof(T)
个字节的连续内存块。例如,
// Initialize a vector of int
std::vector<int> intvec;
// Reserve contigious block of 4 4-byte chunks of memory
intvec.reserve(4); // [ | | | ]
// Filling in the memory chunks has obvious behavior:
intvec.push_back(1); // [1| | | ]
intvec.push_back(2); // [1|2| | ]
然后,我们可以在随机访问时间内访问任何元素,因为如果我们要求向量的第k个元素,我们只需从向量开始的内存地址开始,然后“跳转” k * sizeof(T)
个字节进入第k个元素。
我的心理模型因大小未知/不同的自定义对象而崩溃。例如,
class Foo {
public:
Foo() = default;
Foo(std::vector<int> vec): _vec{vec} {}
private:
std::vector<int> _vec;
};
int main() {
// Initialize a vector Foo
std::vector<Foo> foovec;
// Reserve contigious block of 4 ?-byte chunks of memory
foovec.reserve(4); // [ | | | ]
// How does memory allocation work since object sizes are unkown?
foovec.emplace_back(std::vector<int> {1,2}); // [{1,2}| | | ]
foovec.emplace_back(std::vector<int> {1,2,3,4,5}); // [{1,2}|{1,2,3,4,5}| | ]
return 0;
}
由于我们不知道每个Foo实例的大小,foovec.reserve()
如何分配内存?此外,您如何获得随机访问时间,而我们不知道要“跳”多远才能到达第k个元素?
答案 0 :(得分:8)
您的尺寸概念有缺陷。 std::vector<type>
具有一个编译时已知要占用的空间大小。它还具有可以使用的运行时大小(在运行时分配,并且向量保存指向它的指针)。您可以像这样布置图片
+--------+
| |
| Vector |
| |
| |
+--------+
|
|
v
+-------------------------------------------------+
| | | | | |
| Element | Element | Element | Element | Element |
| | | | | |
+-------------------------------------------------+
因此,当您拥有一个包含矢量的事物的矢量时,每个Element
都将成为矢量,然后这些指向它们自己的存储的点将出现在其他地方,例如
+--------+
| |
| Vector |
| |
| |
+----+---+
|
|
v
+----+----+---------+---------+
| Object | Object | Object |
| with | with | with |
| Vector | Vector | Vector |
+----+----+----+----+----+----+
| | | +---------+---------+---------+---------+---------+
| | | | | | | | |
| | +-->+ Element | Element | Element | Element | Element |
| | | | | | | |
| | +-------------------------------------------------+
| | +-------------------------------------------------+
| | | | | | | |
| +--->+ Element | Element | Element | Element | Element |
| | | | | | |
| +-------------------------------------------------+
| +-------------------------------------------------+
| | | | | | |
+--->+ Element | Element | Element | Element | Element |
| | | | | |
+---------+---------+---------+---------+---------+
这样,所有向量都彼此相邻,但是向量具有的元素可以在内存中的其他任何位置。因此,您不想对矩阵使用std:vector<std::vector<int>>
。所有子向量都可以存储到任何地方,因此行之间没有局部性。
答案 1 :(得分:7)
大小
class Foo {
public:
Foo() = default;
Foo(std::vector<int> vec): _vec{vec} {}
private:
std::vector<int> _vec;
};
是已知且恒定的,内部的 std :: vector 在堆中进行分配,因此foovec.reserve(4);
否则 std :: vector 可以放在堆栈中吗? ;-)
答案 2 :(得分:3)
类Foo
的大小在编译时是已知的,std::vector
类的大小是恒定的,因为它持有的元素是在堆上分配的。
std::vector<int> empty{};
std::vector<int> full{};
full.resize(1000000);
assert(sizeof(empty) == sizeof(full));
std::vector<int>
,empty
和full
的两个实例尽管拥有不同数量的元素,但始终具有相同的大小。
如果您想要一个无法调整大小的数组,并且必须在编译时知道其大小,请使用std::array
。
答案 3 :(得分:2)
当您创建类型为T的向量,然后为该向量保留N个元素时,编译器基本上会找到并保留一个连续的内存块
编译器没有这样做。它生成代码以在运行时从向量的分配器请求存储。默认情况下,它是std::allocator
,它委托给operator new
,它将从运行时系统中获取未初始化的存储。
我的心理模型因大小未知/不同的自定义对象而崩溃
用户定义类型实际上 的唯一方式是大小不完整-您不能将向量声明为不完整类型。
在代码中类型为 完整的任何位置,其大小也是固定的,您可以照常声明一个存储该类型的向量。
您的Foo
已完成,并且其大小在编译时是固定的。您可以使用sizeof(Foo)
和sizeof(foovec[0])
等进行检查。
向量拥有可变数量的存储空间,但在对象中不包含。它只是存储一个指针以及保留和使用的大小(或等效值)。例如,以下实例:
class toyvec {
int *begin_;
int *end_;
size_t capacity_;
public:
// push_back, begin, end, and all other methods
};
总是具有固定的大小sizeof(toyvec) = 2 * sizeof(int*) + sizeof(size_t) + maybe_some_padding
。分配一个巨大的内存块,并将begin
设置为它的开头,对指针本身的大小没有影响。
tl; dr C ++没有动态调整大小的对象。对象的大小由类定义永久固定。 C ++ 有的对象拥有动态存储,并且可能会调整其大小,但这不是对象本身的一部分。