簡體   English   中英

std :: vector如何支持未知大小的自定義對象的連續內存

[英]How does std::vector support contiguous memory for custom objects of unknown size

我正在為正確的思維模式和對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個元素?

您的尺寸概念有缺陷。 一個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>>用於矩陣。 所有子向量都將存儲到任何地方,因此行之間沒有局部性。


請注意,這適用於所有可識別分配器的容器,因為它們不會將元素直接存儲在容器中。 對於std::array ,情況並非如此,因為像原始數組一樣,元素是容器的一部分。 如果您有std::array<int, 20> ,則至少為sizeof(int) * 20個字節。

的大小

class Foo {

public:
    Foo() = default;
    Foo(std::vector<int> vec): _vec{vec} {}

private:
    std::vector<int> _vec;
};

內部常量std :: vector是已知且恆定的,它會在堆中進行分配,因此foovec.reserve(4);沒問題foovec.reserve(4);

否則std :: vector怎么會在堆棧中? ;-)

Foo類的大小在編譯時是已知的, std::vector類具有恆定的大小,因為它持有的元素是在堆上分配的。

std::vector<int> empty{};
std::vector<int> full{};
full.resize(1000000);
assert(sizeof(empty) == sizeof(full));

盡管持有不同數量的元素, std::vector<int>兩個實例, emptyfull都將始終具有相同的大小。

如果您想要一個無法調整大小的數組,並且必須在編譯時知道其大小,請使用std::array

創建T類型的向量並為該向量保留N個元素時,編譯器基本上會找到並保留一個連續的內存塊

編譯器不執行此類操作。 在運行時生成代碼以請求向量的分配器進行存儲。 默認情況下,它是std::allocator ,它委派給operator new ,該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 ++ 確實擁有一些對象,這些對象擁有並且可能會調整動態存儲的大小,但這不是對象本身的一部分。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM