3

我正在使用 C++,我有以下结构:

结构 ArrayOfThese {
  诠释一个;
  诠释 b;
};

结构数据点 {
  诠释一个;
  诠释 b;
  诠释 c;
};

在内存中,我希望在每个 DataPoint 的末尾有 1 个或多个 ArrayOfThese 元素。每个 DataPoint 的 ArrayOfThese 元素的数量并不总是相同。

因为我有大量的数据点要组装然后通过网络流式传输,所以我希望我的所有数据点及其 ArrayOfThese 元素是连续的。为固定数量的 ArrayOfThese 元素浪费空间是不可接受的。

在 C 语言中,我会在 DataPoint 的末尾创建一个元素,声明为ArrayOfThese d[0];,分配一个 DataPoint 加上足够的额外字节用于我拥有的许多 ArrayOfThese 元素,并使用虚拟数组对它们进行索引。(当然,ArrayOfThese 元素的数量必须在 DataPoint 的字段中。)

在 C++ 中,使用放置新的和相同的 0 长度数组是正确的方法吗?如果是这样,placement new 是否保证从同一个内存池对 new 的后续调用将连续分配?

4

11 回答 11

5

由于您正在处理没有构造函数的普通结构,因此您可以恢复为 C 内存管理:

void *ptr = malloc(sizeof(DataPoint) + n * sizeof(ArrayOfThese));
DataPoint *dp = reinterpret_cast<DataPoint *>(ptr));
ArrayOfThese *aotp = reinterpet_cast<ArrayOfThese *>(reintepret_cast<char *>(ptr) + sizeof(DataPoint));
于 2009-10-26T19:46:36.633 回答
3

由于您的结构是 POD,因此您可以像在 C 中那样做。您唯一需要的就是演员表。假设n是要分配的东西的数量:

DataPoint *p=static_cast<DataPoint *>(malloc(sizeof(DataPoint)+n*sizeof(ArrayOfThese)));

如果您的对象具有非平凡的构造函数,则放置 new 确实会出现这种情况。但是它不保证任何分配,因为它不分配自己并且要求内存已经以某种方式分配。相反,它将传入的内存块视为尚未构造的对象的空间,然后调用正确的构造函数来构造它。如果你要使用它,代码可能是这样的。假设DataPointArrayOfThese arr[0]您推荐的成员:

void *p=malloc(sizeof(DataPoint)+n*sizeof(ArrayOfThese));
DataPoint *dp=new(p) DataPoint;
for(size_t i=0;i<n;++i)
    new(&dp->arr[i]) ArrayOfThese;

被构造的东西必须被破坏,所以如果你这样做,你也应该理清析构函数的调用。

(我个人建议在这种情况下使用 POD,因为它不需要调用构造函数和析构函数,但是如果你小心的话,这种事情可以合理安全地完成。)

于 2009-10-26T19:53:26.510 回答
2

正如 Adrian 在他的回答中所说,您在内存中所做的事情不必与您通过网络传输的内容相同。事实上,明确划分这一点甚至可能是件好事,因为如果您以后需要重构数据,那么拥有依赖于以特定方式设计的数据的通信协议会带来很大的问题。

连续存储任意数量元素的 C++ 方法当然是std::vector. 由于您甚至没有考虑到这一点,因此我认为有些东西使这不受欢迎。(您是否只有少量ArrayOfThese并担心与相关的空间开销std::vector?)

虽然过度分配零长度数组的技巧可能不能保证有效,并且在技术上可能会调用可怕的未定义行为,但它是一种广泛传播的行为。你在哪个平台?在 Windows 上,这是在 Windows API 中完成的,因此很难想象供应商会提供不支持此功能的 C++ 编译器。

如果可能的ArrayOfThese元素计数数量有限,您还可以使用fnieto 的技巧来指定这几个数字,然后指定new生成的模板实例之一,具体取决于运行时编号:

struct DataPoint {
  int a;
  int b;
  int c;
};

template <std::size_t sz>
struct DataPointWithArray : DataPoint {
  ArrayOfThese array[sz];
};

DataPoint* create(std::size_t n)
{
  switch(n) {
    case 1: return new DataPointWithArray[1];
    case 2: return new DataPointWithArray[2];
    case 5: return new DataPointWithArray[5];
    case 7: return new DataPointWithArray[7];
    case 27: return new DataPointWithArray[27];
    default: assert(false);
  }
  return NULL;
}
于 2009-10-26T20:47:17.913 回答
1

在 C++0X 之前,该语言没有内存模型可言。对于新标准,我不记得有任何关于连续性保证的讨论。

关于这个特定的问题,听起来你想要的是一个池分配器,存在许多这样的例子。例如,考虑一下Alexandrescu 的Modern C++ Design。小对象分配器讨论是你应该看的。

于 2009-10-26T19:39:41.547 回答
1

我认为boost::variant可以做到这一点。我还没有机会使用它,但我相信它是联合的包装,所以std::vector它们中的一个应该是连续的,但是当然每个项目都会占用两个尺寸中较大的一个,你不能有一个矢量具有不同大小的元素。

看看boost::variant 和 boost::any 的比较

如果您希望每个元素的偏移量依赖于先前元素的组合,则必须编写自己的分配器和访问器。

于 2009-10-26T19:43:58.183 回答
1

似乎分配一个指针数组并使用它而不是使用placement new会更简单。这样您就可以将整个数组重新分配到新的大小,而运行时成本很低。此外,如果您使用placement new,则必须显式调用析构函数,这意味着将非放置和放置混合在一个数组中是危险的。在你做任何事情之前阅读http://www.parashift.com/c++-faq-lite/dtors.html 。

于 2009-10-26T19:48:12.417 回答
1

不要混淆程序内部的数据组织和序列化的数据组织:它们的目标不同。

对于通过网络进行流式传输,您必须考虑通道的双方,即发送方和接收方:接收方如何区分 DataPoint 和 ArrayOfThese ?接收方如何知道在 DataPoint 之后附加了多少 ArrayOfThese ?(还要考虑:每一边的字节顺序是什么?数据类型在内存中的大小是否相同?)

就个人而言,我认为您需要一种不同的结构来流式传输数据,在其中添加要发送的 DataPoint 的数量以及每个 DataPoint 之后的 ArrayOfThese 的数量。我也不关心数据已经在我的程序中组织的方式,并重新组织/重新格式化以适应我的协议而不是我的程序。之后编写一个发送函数和另一个接收函数并不是什么大问题。

于 2009-10-26T20:01:12.187 回答
1

为什么不让DataPoint包含ArrayOfThese项的可变长度数组?这将在 C 或 C++ 中工作。如果任一结构包含非原始类型,则存在一些问题

但是在结果上使用free()而不是delete

struct ArrayOfThese {
  int a;
  int b;
};


struct DataPoint {
  int a;
  int b;
  int c;
  int length;
  ArrayOfThese those[0];
};

DataPoint* allocDP(int a, int b, int c, size_t length)
{
    // There might be alignment issues, but not for most compilers:
    size_t sz = sizeof(DataPoint) + length * sizeof(ArrayOfThese);
    DataPoint dp = (DataPoint*)calloc( sz );
    // (Check for out of memory)
    dp->a = a; dp->b = b; tp->c = c; dp->length = length;
}

然后,您可以在 DataPoint 知道其长度的循环中“正常”使用它:

DataPoint *dp = allocDP( 5, 8, 3, 20 );

for(int i=0; i < dp->length; ++i)
{
    // Initialize or access: dp->those[i]
}
于 2009-10-26T20:31:01.207 回答
0

你能把它们变成具有相同超类的类,然后使用你最喜欢的 stl 容器,使用超类作为模板吗?

于 2009-10-26T19:35:44.470 回答
0

两个问题:

  1. ArrayOfThese 和 DataPoint 之间的相似性是真实的,还是发布的简化?即,真正的区别只是一个整数(或一些任意数量的相同类型的项目)?
  2. 与特定 DataPoint 关联的 ArrayOfThese 的数量在编译时是否已知?

如果第一个是真的,我会认真考虑简单地为一个 DataPoint+N ArrayOfThese 分配一个包含尽可能多的项目的数组。然后我会快速构建一些代码来重载 operator[] 以返回项目 N+3,并重载 a()、b() 和 c() 以返回前三个项目。

如果第二个是真的,我基本上会建议我看到 fnieto 刚刚发布的内容,所以我不会详细说明。

至于placement new,它并不能真正保证关于分配的任何事情——事实上,关于placement new 的整个想法是它与内存分配完全无关。相反,它允许您在已分配的内存块中的任意地址(受对齐限制)创建对象。

于 2009-10-26T19:55:17.253 回答
0

这是我最终编写的代码:

#include <iostream>
#include <cstdlib>
#include <cassert>
using namespace std;

struct ArrayOfThese {
  int e;
  int f;
};

struct DataPoint {
  int a;
  int b;
  int c;
  int numDPars;
  ArrayOfThese d[0];

  DataPoint(int numDPars) : numDPars(numDPars) {}

  DataPoint* next() {
    return reinterpret_cast<DataPoint*>(reinterpret_cast<char*>(this) + sizeof(DataPoint) + numDPars * sizeof(ArrayOfThese));
  }

  const DataPoint* next() const {
    return reinterpret_cast<const DataPoint*>(reinterpret_cast<const char*>(this) + sizeof(DataPoint) + numDPars * sizeof(ArrayOfThese));
  }
};

int main() {
  const size_t BUF_SIZE = 1024*1024*200;

  char* const buffer = new char[BUF_SIZE];
  char* bufPtr = buffer;

  const int numDataPoints = 1024*1024*2;
  for (int i = 0; i < numDataPoints; ++i) {
    // This wouldn't really be random.
    const int numArrayOfTheses = random() % 10 + 1;

    DataPoint* dp = new(bufPtr) DataPoint(numArrayOfTheses);

    // Here, do some stuff to fill in the fields.
    dp->a = i;

    bufPtr += sizeof(DataPoint) + numArrayOfTheses * sizeof(ArrayOfThese);
  }

  DataPoint* dp = reinterpret_cast<DataPoint*>(buffer);
  for (int i = 0; i < numDataPoints; ++i) {
    assert(dp->a == i);
    dp = dp->next();
  }

  // Here, send it out.

  delete[] buffer;

  return 0;
}
于 2009-10-27T20:01:12.687 回答