实现与模板和基类柔性阵列成员(Implementing flexible array members

2019-10-18 07:32发布

在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)?

我不感兴趣,在这两个平凡的解决方案(包括在基类的额外指针数组的开始,并在堆上分配的数组)。

Answer 1:

你想要做什么是可能的,不是不容易的,在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;

那么,一些类型的,我们将解释的话说“的标题部分之后,我们有一个数组”。 我想大规模改善这种语法,但对于现在的重要组成部分,是建立编译时间列表:

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

然后你不得不写了一些给定模板的魔力在于计算出, N在运行时,你需要一个缓冲有多大存储intdouble后跟N的副本std::string ,一切都正确对齐。 这是不容易的。

一旦你这样做,你也需要写的是构建上述一切代码。 如果你想获得幻想,你甚至露出完美转发构造和非默认状态下构建构造允许包装的对象。

最后,写上去,用于查找所述存储器基于I注入到上述标签的构造的对象的偏移量的界面tuple S, reinterpret_cast S中的原始存储器插入到数据类型的引用,并返回该引用(在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

这可能与一些超载加以改进:

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结构布局系统,一旦你已完成,这是不难“末任意长度阵列”注入。 它甚至还可以在中间注入任意长度的数组(但是这意味着要找到结构成员的地址,过去那种阵列是一个运行时的问题:然而,作为我们operator^允许运行任意代码,你的结构存储阵列的长度,我们能够做到这一点)。

我不能,但是,想到了一个简单的,可移植的方法去做你的C ++,这里的数据类型存储的数据并不必须是标准布局中问什么。



Answer 2:

随着一点点的类型转换,您可以使用C ++模式C为好。

只是使阵列初始大小之一,并使用分配的结构的指针new char[...]

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

Foo* foo_ptr = reinterpret_cast<Foo*>(new char[sizeof(Foo) + sizeof(int) * (n - 1)]);

然后,你当然应该释放的结构时,也投它:

delete[] reinterpret_cast<char*>(foo_ptr);

我真的不推荐这种用于一般用途,但。 唯一可以接受的(我)的地方,转移结构,当以某种方式(网络或文件)使用的方案,例如,因为这是。 然后我推荐它从一个“适当的” C编组到/ ++与对象std::vector的可变长数据。



Answer 3:

你想要做什么是根本不可能在C ++中。 其原因是,的sizeof(T)是编译时间常数,这样,将一个阵列内的类型使其具有编译时的大小。 所以,适当的C ++做这件事的方式保持的类型以外的数组。 请注意,将阵列堆栈是唯一可能的,如果它是某种类型的内部。 所以一切都基于堆栈被限制在编译时数组的大小。 (ALLOCA可能会解决这个问题)。 你原来的C版本也有类似的问题,这类型不能处理运行时大小的数组。

这也是处理在C ++中的可变长度阵列。 不支持,因为它打破的sizeof和C ++类依赖的sizeof数据成员的访问。 不能使用C ++类一起使用的任何解决方案是没有好处的。 的std ::矢量有没有这样的问题。

注意在C ++ 11 constexpr使得偏移计算在您的自定义数据类型相当简单 - 编译时的限制仍然存在。



Answer 4:

我知道我有点晚了这里,但我sugestion将是:

template<size_t N>
struct Foo {
    int var1;
    std::array<int,N> var2;
};

std::array将数据存储为int v[N]; (未在堆),所以不会有将其转换成字节流的问题



Answer 5:

我也有点晚了,但这种方法是使用C灵活的阵列兼容(如果你当然预处理玩):

#include <cstdlib>
#include <iostream>

using namespace std;

template <typename T>
class Flexible 
{
public:
   Flexible(){}
   ~Flexible(){}
   inline T & operator[](size_t ind){
      return reinterpret_cast<T*>(this)[ind];
   }
   inline Flexible<T> * getThis() { return this; }
   inline operator T * () { return reinterpret_cast<T*>(this); }
};

struct test{
   int a;
   Flexible<char> b;
};

int main(int argc, char * argv[]){
   cout << sizeof(test) << endl;
   test t;
   cout << &t << endl;
   cout << &t.a << endl;
   cout << &t.b << endl;
   cout << t.b.getThis() << endl;
   cout << (void*)t.b << endl;
   test * t2 = static_cast<test*>(malloc(sizeof(test) + 5));
   t2->b[0] = 'a';
   t2->b[1] = 'b';
   t2->b[2] = 0;
   cout << t2->b << endl;
   return 0;
}

(关于测试GCC,并用铛clang++ -fsanitize=undefined ,我认为没有理由它不会是标准,除了reinterpret_cast部分...)

注意:如果不是该结构的最后一个字段,你不会得到一个错误。 要格外谨慎,在装有该结构作为子子-...-子成员对象用这个,因为你可能无意中后添加其他领域,并得到一些莫名其妙的错误。 例如,我不会建议限定与成员的结构/类其本身包含一个Flexible ,像这样的:

class A{
  Flexible<char> a;
};

class B{
  A a;
};

因为它很容易后做这个错误:

class B{
  A a;
  int i;
};


文章来源: Implementing flexible array members with templates and base class