我正在使用C ++,我有以下结构:
struct ArrayOfThese { int a; int b; }; struct DataPoint { int a; int b; int c; };
在内存中,我想在每个DataPoint的末尾有一个或多个ArrayOfThese元素。每个DataPoint并不总是具有相同数量的ArrayOfThese元素。
因为我有一些荒谬的DataPoints要汇编然后通过网络传输,我希望我的所有DataPoints和它们的ArrayOfThese元素都是连续的。为固定数量的ArrayOfThese元素浪费空间是不可接受的。
在C中,我会在DataPoint的末尾创建一个声明为ArrayOfThese d[0];
的元素,分配一个DataPoint加上足够的额外字节,无论我有多少个ArrayOf这些元素,并使用虚拟数组索引到他们。 (当然,ArrayOfThese元素的数量必须在DataPoint的字段中。)
在C ++中,正在使用placement new和相同的0-length数组破解正确的方法?如果是这样,放置新保证后续从同一内存池调用new会连续分配吗?
答案 0 :(得分: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));
答案 1 :(得分:3)
由于你的结构是POD,你可以像在C中那样做。你唯一需要的就是施法。假设n
是要分配的内容的数量:
DataPoint *p=static_cast<DataPoint *>(malloc(sizeof(DataPoint)+n*sizeof(ArrayOfThese)));
如果您的对象具有非平凡的构造函数,那么 Placement new确实会出现这种情况。它不保证任何分配,因为它没有分配自己,并且要求内存已经以某种方式分配。相反,它将作为空间传入的内存块视为尚未构造的对象,然后调用正确的构造函数来构造它。如果您要使用它,代码可能会像这样。假设DataPoint
拥有您建议的ArrayOfThese 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,因为它不需要调用构造函数和析构函数,但如果你小心的话,这种事情可以合理安全地完成。)
答案 2 :(得分:2)
正如Adrian在his answer中所说,你在内存中所做的事情不一定与你在网络上传输的内容相同。事实上,明确区分这一点可能会更好,因为如果您以后需要重构数据,那么依赖于您的数据以特定方式设计的通信协议会产生巨大的问题。
连续存储任意数量元素的C ++方法当然是std::vector
。既然你甚至没有考虑过这个问题,我认为有些东西会让人觉得不合时宜。 (你只有少量的ArrayOfThese
,并担心与std::vector
相关的空间开销吗?)
虽然过度分配零长度数组的技巧可能无法保证工作,但从技术上讲,可能会调用可怕的未定义行为,但这是一个广泛传播的行为。你在什么平台上?在Windows上,这是在Windows API中完成的,因此很难想象供应商会提供不支持此功能的C ++编译器。
如果可能的ArrayOfThese
元素数量有限,您还可以使用fnieto's trick指定这几个数字,然后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;
}
答案 3 :(得分:1)
在C ++ 0X之前,该语言有 no 内存模型可供使用。而且,根据新标准,我不记得任何有关邻接保证的言论。
关于这个特定的问题,听起来好像你想要的是一个池分配器,其中有很多例子。例如,考虑一下Alexandrescu的Modern C++ Design。小对象分配器讨论是你应该看到的。
答案 4 :(得分:1)
我认为boost::variant
可能会实现这一目标。我没有机会使用它,但我相信它是工会的包装,因此std::vector
它们应该是连续的,但当然每个项目将占用两种尺寸中较大的一种,你不能有一个具有不同大小元素的向量。
查看comparison of boost::variant and boost::any。
如果希望每个元素的偏移量取决于前面元素的组成,则必须编写自己的分配器和访问器。
答案 5 :(得分:1)
似乎分配一个指针数组并使用它更简单,而不是使用placement new。这样你就可以将整个数组重新分配到新的大小而运行成本很低。此外,如果您使用placement new,则必须明确调用析构函数,这意味着在单个数组中混合非放置和放置是危险的。在做任何事情之前,请先阅读http://www.parashift.com/c++-faq-lite/dtors.html。
答案 6 :(得分:1)
不要混淆程序和数据组织中的数据组织以进行序列化:它们没有相同的目标。
要通过网络进行流式传输,您必须考虑通道的两端,发送方和接收方:接收方如何区分DataPoint和ArrayOfThese?接收方如何知道在DataPoint之后附加了多少个ArrayOfThese? (还要考虑:每一方的字节顺序是什么?数据类型在内存中的大小是否相同?)
就个人而言,我认为您需要一种不同的数据流结构,您可以在其中添加您发送的DataPoint数量以及每个DataPoint之后的ArrayOfThese数量。我也不关心我的程序中数据的组织方式,并重新组织/重新格式化以适应我的协议,而不是我的程序。之后写一个发送功能和另一个接收功能并不是什么大问题。
答案 7 :(得分: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]
}
答案 8 :(得分:0)
你能用相同的超类将它们变成类,然后使用你喜欢的stl容器,使用超类作为模板吗?
答案 9 :(得分:0)
两个问题:
如果第一个是真的,我会认真考虑简单地为一个DataPoint + N ArrayOfThese分配一个尽可能多的项目数组。然后我构建一个快速的代码来重载operator []以返回项目N + 3,并重载a(),b()和c()以返回前三项。
如果第二个是真的,我基本上会建议fnieto刚刚发布的内容,所以我不会详细介绍。
就新位置而言,它并不真正保证分配的任何内容 - 实际上,关于新位置的整个想法是它与内存分配完全无关。相反,它允许您在已经分配的内存块中的任意地址(受到对齐限制)创建对象。
答案 10 :(得分: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;
}