使用模板和基类实现灵活的数组成员

jle*_*ahy 1 c++ flexible-array-member

在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;
}
Run Code Online (Sandbox Code Playgroud)

但不仅是这个糟糕的C++,它也是非法的.

您可以在C++中实现类似的效果,如下所示:

struct FooBase {
    void dostuff();

    int var1;
    int var2[1];
};

template<size_t N>
struct Foo : public FooBase {
    int var2[N-1];
};
Run Code Online (Sandbox Code Playgroud)

虽然这将工作(你可以访问FooBase的方法var2[2],var2[3]等等),它依赖于Foo作为标准配置,这是不是很漂亮.

这样做的好处是非模板化函数可以Foo*通过采用FooBase*和调用操作的方法来接收任何没有转换的函数var2,并且内存是连续的(这可能是有用的).

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

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

Yak*_*ont 5

你想要做的事情是可能的,而不是简单的,在C++中,你的界面struct不是一个struct样式界面.

就像一个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;
Run Code Online (Sandbox Code Playgroud)

然后,我们将解释为"在标题部分之后,我们有一个数组"的更多类型.我想大量改进这种语法,但现在重要的部分是建立编译时列表:

struct arr_t {} arr;
std::tuple< header_t, std::pair< arr_t, std::string > > full_t;
Run Code Online (Sandbox Code Playgroud)

然后你必须编写一些模板mojo,N在运行时给出一个缓冲区,你需要存储多大的缓冲区int,double然后是所有正确对齐的N副本std::string.这并不容易.

完成后,您还需要编写构造上述所有内容的代码.如果你想获得幻想,你甚至会暴露出一个完美的转发构造函数和构造函数包装器,允许在非默认状态下构造对象.

最后,编写一个接口,根据我注入上述tuples 的标记找到构造对象的内存偏移量,reinterpret_cast将原始内存转换为对数据类型的引用,并返回该引用(在const和非const中)版本).

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

如果你看看如何std::vector将内存块转换为数组,并将其与如何boost::mpl安排标记到数据的地图混合,然后手动混乱以保持正确对齐,每一步都具有挑战性但并非不可能.我在这里使用的凌乱语法也可以改进(在某种程度上).

结束界面可能是

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
Run Code Online (Sandbox Code Playgroud)

可以通过一些重载来改进:

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
Run Code Online (Sandbox Code Playgroud)

但是所涉及的努力相当大,以实现这一目标,并且所需的许多事情都会非常糟糕.

基本上,为了解决你所描述的问题,我必须在C++中手动重建整个C++/C结构布局系统,一旦你完成了这一点,就不难在最后注入"任意长度数组" .甚至可以在中间注入任意长度的数组(但这意味着找到结构成员通过该数组的地址是一个运行时问题:但是,因为我们operator^可以运行任意代码,并且您的结构可以存储数组的长度,我们能够做到这一点).

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