【问题标题】:Replace vector of vector with flat memory structure用平面内存结构替换向量的向量
【发布时间】:2017-04-26 04:36:34
【问题描述】:

我有以下类型:

std::vector<std::vector<int>> indicies

其中内部向量的大小始终为 2。问题是,向量在内存中是不连续的。我想用一些连续的东西替换内部向量,以便我可以投射扁平数组:

int *array_a = (int *) &(a[0][0])

如果新类型有 [] 运算符就好了,这样我就不必更改整个代码。 (如有必要,我也可以自己实现)。我的想法是:

std::vector<std::array<int, 2>>

std::vector<std::pair<int, int>>

这些在内存中看起来如何?我写了一个小测试:

#include <iostream>
#include <array>
#include <vector>
int main(int argc, char *argv[])
{
    using namespace std;

    vector<array<int, 2>> a(100);

    cout << sizeof(array<int, 2>) << endl;

    for(auto i = 0; i < 10; i++){
        for(auto j = 0; j < 2; j++){
            cout << "a[" << i << "][" << j << "] " 
                <<&(a[i][j]) << endl;
        }
    }
    return 0;
}

导致:

8
a[0][0] 0x1b72c20
a[0][1] 0x1b72c24
a[1][0] 0x1b72c28
a[1][1] 0x1b72c2c
a[2][0] 0x1b72c30
a[2][1] 0x1b72c34
a[3][0] 0x1b72c38
a[3][1] 0x1b72c3c
a[4][0] 0x1b72c40
a[4][1] 0x1b72c44
a[5][0] 0x1b72c48
a[5][1] 0x1b72c4c
a[6][0] 0x1b72c50
a[6][1] 0x1b72c54
a[7][0] 0x1b72c58
a[7][1] 0x1b72c5c
a[8][0] 0x1b72c60
a[8][1] 0x1b72c64
a[9][0] 0x1b72c68
a[9][1] 0x1b72c6c

在这种情况下似乎有效。这是标准中的行为还是只是幸运的巧合?有没有更好的方法来做到这一点?

【问题讨论】:

标签: c++ c++11 vector stl


【解决方案1】:

array&lt;int,2&gt; 将是一个包含数组int[2] 的结构;标准并没有直接强制要求,但确实没有其他理智和实用的方法可以做到这一点。

参见标准中的 23.3.7 [array]。我发现标准中没有任何内容要求 sizeof(std::array&lt;char, 10&gt;)==1024 为假。这将是一个荒谬的 QOI(实施质量);我见过的每个实现都有sizeof(std::array&lt;T,N&gt;) == N*sizeof(T),以及其他我认为是敌对的。

数组必须是连续的容器,它们是可以由最多可转换为T 类型的N 参数初始化的聚合。

标准允许在这样的数组之后进行填充。我知道有 0 个编译器插入了这种填充。

不保证可以安全访问连续的std::array&lt;int,2&gt; 缓冲区作为int 的平面缓冲区。事实上,别名规则几乎肯定会禁止这种未定义行为的访问。你甚至不能用int[3][7] 做到这一点! See this SO question and answerand herehere

大多数编译器都会使您描述的工作正常,但优化器可能会决定通过int* 和通过array&lt;int,2&gt;* 的访问不能访问相同的内存,并产生疯狂的结果。这似乎不值得。

一种符合标准的方法是编写一个数组视图类型(它采用两个指针并形成一个带有[] 重载的可迭代范围)。然后写一个平面缓冲区的二维视图,较低的维度是运行时或编译时值。然后它的[] 会返回一个数组视图。

boost 和其他“标准扩展”库中将有代码为您执行此操作。

将 2d 视图与拥有向量的类型合并,即可获得 2d 向量。

唯一的行为区别是,当向量代码的旧向量复制较低维度(如auto inner=outer[i])时,它会复制数据,然后它会创建一个视图。

【讨论】:

  • IMO 允许将由 int[2] 组成的结构别名为整数
  • @m.m 但不是包含 2 个此类数组的结构(布局兼容性/相同地址指第一个成员),也不是包含 2 个此类结构的数组作为 int 的一个缓冲区。所以int* ptr=?; ptr[3];不能在标准下以定义的方式引用任何array&lt;int,2&gt;的成员,不管?是什么。
  • 标准要求数组在其元素之间没有填充。这递归地适用于数组数组。 int[4] 和 int[2][2] 和 array[2] 的布局定义是一样的。
  • @max 是否要求结构在其元素之后 没有填充?即,struct A{ char x[2]; }; 必须有sizeof(A)==2?如果有,在哪里?
  • en.cppreference.com/w/c/language/sizeof :当应用于具有结构或联合类型的操作数时,结果是此类对象中的总字节数,包括内部和尾随填充。尾随填充使得如果对象是数组的元素,则该数组的下一个元素的对齐要求将得到满足,换句话说,sizeof(T) 返回 T[] 数组元素的大小.
【解决方案2】:

有没有更好的方法来做到这一点?

我最近完成了另一个版本的 Game-of-Life。

游戏板是二维的,是的,向量的向量在其中浪费了空间。

在我最近的努力中,我选择为 2d 游戏板尝试 1d 矢量。

typedef std::vector<Cell_t*>  GameBoard_t;

然后我创建了一个简单的索引函数,以便在使用 row/col 时增加代码的可读性:

inline size_t gbIndx(int row, int col)
  { return ((row * MAXCOL) + col); }

示例:访问第 27 行,第 33 列:

Cell_t* cell = gameBoard[ gbIndx(27, 33) ];

gameBoard 中的所有 Cell_t* 现在都被背靠背打包(向量的定义),并且可以使用 gbIndx() 按行/列顺序访问(初始化、显示等)。


此外,我可以将简单索引用于各种工作:

void setAliveRandom(const GameBoard_t& gameBoard)
{
   GameBoard_t  myVec(m_gameBoard); // copy cell vector

   time_t seed = std::chrono::system_clock::
        now().time_since_epoch().count();

   // randomize copy's element order
   std::shuffle (myVec.begin(), myVec.end(), std::default_random_engine(seed));

   int count = 0;
   for ( auto it : myVec )
   {  
      if (count & 1)  it->setAlive(); // touch odd elements
      count += 1;
   }
}

我对不需要行/列索引的频率感到惊讶。

【讨论】:

  • g++ v6.2 告诉我“std::default_random_engine(...)”是实现定义的。我现在使用 std::mt19937_64 gen(rd) 和 std::random_device rd;
【解决方案3】:

据我所知,std::vector 在内存中是连续的。看看这个问题:

Why is std::vector contiguous?,

Are std::vector elements guaranteed to be contiguous?

如果您必须调整内部向量的大小,则整个结构不会是连续的,但内部向量仍然是它。但是,如果您使用向量的向量,您将拥有一个完全连续的结构(我在这里进行编辑,抱歉我误解了您的问题),这意味着指向您的内部向量的指针也将是连续的。

如果你想实现一个总是连续的结构,从第一个向量的第一个元素到最后一个向量的最后一个元素,你可以将它实现为具有vector&lt;int&gt;elems_per_vector的自定义类表示每个内部向量中的元素数。

然后,您可以重载operator(),因此要访问a(i,j),您实际上是在访问a.vector[a.elems_per_vector*i+j]。但是,要插入新元素,并且为了使它们之间的内部向量保持恒定大小,您必须进行与您拥有的内部向量一样多的插入操作。

【讨论】:

  • 一个 ´std::vector` 连续存储其元素,但不是本地存储。也就是说,在一个向量的向量中,每个内向量连续存储其元素,但一个内向量的最后一个元素一般不会与下一个内向量的第一个元素连续存储,这正是OP所要求的。 /跨度>
  • @J. Checa 但是,如果你使用向量的向量,你将拥有一个完全连续的结构 这是错误的。向量的向量实际上连续存储内部向量(作为对象)。但是,每个内部向量在内部存储一个表示其数据的指针。指针本身并不能保证指向连续的内存区域。
  • 对不起,我误解了 OP 的问题。他似乎希望所有元素,从第一个向量的第一个元素,到最后一个向量的最后一个元素,在内存中对齐。是的,确实,如果您创建一个向量向量,您将获得一个连续的内存区域,其中包含指向内部向量的指针。另外,我的最后一句话导致了混乱,我的意思是说你的所有元素都是连续的,指的是指针本身。我将编辑我的答案以避免混淆。感谢@vsoftco 和 Wintermute 的 cmets :D
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-08-14
  • 2021-12-21
  • 2016-08-16
  • 1970-01-01
  • 2020-09-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多