使用模板和基类实现灵活的数组成员 [英] Implementing flexible array members with templates and base class

查看:99
本文介绍了使用模板和基类实现灵活的数组成员的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

Foo * f = malloc(sizeof(struct Foo)+ sizeof(int)* n);
for(int i = 0; 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 + +,和你的结构的接口

code>不是 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;

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

  struct arr_t {} arr; 
std :: tuple< header_t,std :: pair< arr_t,std :: string> > full_t;

然后你必须写出一些模板mojo, > N 在运行时,需要多大的缓冲区来存储 int double 后跟 std :: string 的副本 N ,一切正确对齐。这不容易。



一旦你这样做,你还需要编写构建上述所有内容的代码。如果你想获得花哨,你甚至会暴露一个完美的转发构造函数和构造函数包装器允许对象在非默认状态下构造。



最后,写基于注入到上述 tuple s中的标签, reinterpret_cast

对于最后的数组,你会返回一些重载了 operator [] 的临时数据结构,产生引用。



看看如何 std :: vector 将内存块转换成数组,并与 boost :: mpl 安排标签到数据映射,然后也手工混乱,保持事物正确对齐,每一步都是具有挑战性的,但不是不可能。



结束界面可能是

  Foo * my_data = Foo :: Create(7); 
my_data-> get< tag1_t>(); // returns an int
my_data-> get< tag2_t>(); //返回double
my_data-> get< arr_t>()[3]; //访问第三个

这可以改进一些重载到

  Foo * my_data = Foo :: Create(7); 
int x = my_data ^ tag1; //返回一个int
double y = my_data ^ tag2; //返回double
std :: string z = my_data ^ arr [3]; //访问第3个std :: string

但是所涉及的工作量会相当大



基本上,为了解决你所描述的问题,我需要重建整个C ++ / C结构 - 布局系统手动在C + +,一旦你做了,不难注入任意长度数组在结束。甚至可以在中间插入任意长度的数组(但这意味着找到超过该数组的结构成员的地址是一个运行时问题:然而,因为我们的 operator ^ 允许运行任意代码,并且你的结构可以存储数组的长度,我们可以这样做)。



然而,我不能想到一个更简单,可移植的方式来做你在C ++中所要求的,其中存储的数据类型不必是标准布局。


In C99, you commonly see the following pattern:

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;
}

But not only is this bad C++, it's also illegal.

You can achieve a similar effect in C++ like this:

struct FooBase {
    void dostuff();

    int var1;
    int var2[1];
};

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

Although this will work (in the methods of FooBase you can access var2[2], var2[3], etc) it relies on Foo being standard layout, which isn't very pretty.

The benefit of this is that a non-templated function can receive any Foo* without conversion by taking a FooBase* and call methods that operate on var2, and the memory is all contiguous (which can be useful).

Is there a better way of achieving this (which is legal C++/C++11/C++14)?

I'm not interested in the two trivial solutions (including an extra pointer in the base class to the start of the array, and allocating the array on the heap).

解决方案

What you want to do is possible, not not easy, in C++, and the interface to your struct is not a struct style interface.

Just like how a std::vector takes a block of memory and reformats it into something very much like an array, then overloads operators to make itself look array-like, you can do the same.

Access to your data will be via accessors. You'll manually construct your members in the buffer.

You might start with a list of pairs of "tags" and data types.

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

then, some more types that we'll interpret as saying "after the header part, we have an array". I'd want to massively improve this syntax, but the important part for now is to build up compile time lists:

struct arr_t {} arr;
std::tuple< header_t, std::pair< arr_t, std::string > > full_t;

You'd then have to write up some template mojo that figures out, given N at run time, how big a buffer you'd need to store the int and double followed by N copies of the std::string, everything properly aligned. This isn't easy.

Once you've done that, you'd also need to write code that constructs everything described above. If you wanted to get fancy, you'd even expose a perfect forwarding constructor and constructor wrappers allowing the objects to be constructed in a non-default state.

Finally, write up an interface that finds the memory offset of the constructed objects based on the tags I injected into the above tuples, reinterpret_casts the raw memory into a reference to the data type, and returns that reference (in both const and non-const versions).

For the array at the end, you'd return some temporary data structure that has overloaded operator[] which produces the references.

If you take a look at how std::vector turns blocks of memory into arrays, and mix that with how boost::mpl arranges tag-to-data maps, and then also mess manually arround with keeping things properly aligned, every step is challenging but not impossible. The messy syntax I've used here can also be improved (to some extent).

The end interface might be

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

which could be improved with some overloading to:

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

but the effort involved would be reasonably large to get this far, and many of the things required would be pretty horrible.

Basically, in order to solve your problem as described, I would have to rebuild the entire C++/C structure-layout system manually within C++, and once you have done that it isn't hard to inject "arbitrary length array at the end". It would even be possible to inject arbitrary length arrays in the middle (but that would mean that finding the address of structure members past that array is a runtime problem: however, as our operator^ is allowed to run arbitrary code, and your structure can store the length of arrays, we are able to do this).

I cannot, however, think of a simpler, portable way to do what you ask within C++, where the data types stored do not have to be standard-layout.

这篇关于使用模板和基类实现灵活的数组成员的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆