在C99中,您通常会看到以下模式:

struct Foo {
    int var1;
    int var2[];
};

Foo * f = malloc(sizeof(struct Foo) + sizeof(int)*n);
for (int i=0; i<n; ++i) {
    f->var2[i] = p;
}

但是,这不仅是糟糕的C++,而且也是非法的。

您可以在C++中实现类似的效果,如下所示:
struct FooBase {
    void dostuff();

    int var1;
    int var2[1];
};

template<size_t N>
struct Foo : public FooBase {
    int var2[N-1];
};

尽管这是可行的(在FooBase的方法中,您可以访问var2[2]var2[3]等),但是它依赖于Foo是标准布局,这不是很漂亮。

这样做的好处是,非模板函数可以通过获取Foo*并调用对FooBase*进行操作的方法来接收任何var2而不进行转换,并且内存都是连续的(这很有用)。

有没有更好的方法可以实现这一点(合法的C++ / C++ 11 / C++ 14)?

我对这两个简单的解决方案不感兴趣(在基类中包括指向数组开头的额外指针,并在堆上分配数组)。

最佳答案

在C++中,您想做的事情是可能的,而不是不容易的,并且struct的接口(interface)不是struct样式的接口(interface)。

就像std::vector占用一块内存并将其重新格式化为非常类似于数组的内容,然后重载运算符以使其自身看起来像数组一样,您可以执行相同的操作。

将通过访问器访问您的数据。您将在缓冲区中手动构造成员。

您可以从“标记”和数据类型对的列表开始。

struct tag1_t {} tag1;
struct tag2_t {} tag2;
typedef std::tuple< std::pair< tag1_t, int >, std::pair<tag2_t, double> > header_t;

然后,我们将解释为更多类型,例如“在 header 部分之后,我们有一个数组”。我想大量改进此语法,但是目前重要的部分是建立编译时间列表:
struct arr_t {} arr;
std::tuple< header_t, std::pair< arr_t, std::string > > full_t;

然后,您必须编写一些模板mojo才能弄清楚,在运行时给定N,需要多大的缓冲区来存储intdouble,然后再存储Nstd::string副本,所有内容都正确对齐。这不容易。

完成此操作后,您还需要编写构成上述所有内容的代码。如果想花哨的话,甚至可以公开一个完美的转发构造函数和构造函数包装器,以使对象可以以非默认状态构造。

最后,编写一个接口(interface),根据我注入(inject)到上面的tuple中的标签找到构造对象的内存偏移,reinterpret_cast将原始内存插入到对数据类型的引用中,并返回该引用(在const和non中-const版本)。

对于最后的数组,您将返回一些临时数据结构,该结构已重载了生成引用的operator[]

如果您查看std::vector如何将内存块转换为数组,然后将其与boost::mpl排列标签到数据的映射方式混合在一起,然后又手动困惑以保持正确对齐,则每个步骤都具有挑战性,但并非不可能。我在这里使用的凌乱语法也可以得到改进(在某种程度上)。

最终接口(interface)可能是
Foo* my_data = Foo::Create(7);
my_data->get<tag1_t>(); // returns an int
my_data->get<tag2_t>(); // returns a double
my_data->get<arr_t>()[3]; // access to 3rd one

可以通过一些重载来改进它:
Foo* my_data = Foo::Create(7);
int x = my_data^tag1; // returns an int
double y = my_data^tag2; // returns a double
std::string z = my_data^arr[3]; // access to 3rd std::string

但是要实现这一目标,所涉及的工作将相当大,并且所需的许多事情将非常可怕。

基本上,为了解决您描述的问题,我将不得不在C++中手动重建整个C++ / C结构布局系统,一旦完成,就不难注入(inject)“最后的任意长度数组” 。甚至有可能在中间插入任意长度的数组(但这将意味着找到超出该数组的结构成员的地址是运行时的问题:但是,由于我们的operator^允许运行任意代码,因此您的结构可以存储数组的长度,我们能够做到)。

但是,我无法想到一种更简单,可移植的方式来执行您在C++中所要求的操作,因为在C++中,存储的数据类型不必是标准布局。

关于c++ - 使用模板和基类实现灵活的数组成员,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/17424731/

10-11 19:02