2

在 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标准布局,这不是很漂亮。

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

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

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

4

5 回答 5

6

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

就像 a 如何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在运行时计算出您需要存储多大的缓冲区intdouble然后是 的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

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

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++ 中提出的要求,其中存储的数据类型不必是标准布局。

于 2013-07-02T12:55:13.790 回答
2

通过一点类型转换,您也可以在 C++ 中使用 C 模式。

只需将数组初始大小设为 1,然后使用以下命令分配结构指针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可变长度数据。

于 2013-07-02T11:49:14.077 回答
0

你想做的事在 C++ 中根本不可能。原因是 sizeof(T) 是编译时常量,因此将数组放在类型中使其具有编译时大小。因此,正确的 c++ 方法将数组保持在类型之外。请注意,只有在某种类型的内部才能将数组放入堆栈。因此,基于堆栈的所有内容都仅限于数组的编译时大小。(alloca 可能会解决这个问题)。您原来的 C 版本也有类似的问题,即类型无法处理运行时大小的数组。

这也是 C++ 中对可变长度数组的处理。不支持,因为它破坏了 sizeof 并且 c++ 类依赖于 sizeof 进行数据成员访问。任何不能与 c++ 类一起使用的解决方案都是不好的。std::vector 没有这样的问题。

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

于 2013-07-02T12:00:44.030 回答
0

我知道我在这里有点晚了,但我的建议是:

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

std::array将数据存储为int v[N];(不在堆中),因此将其转换为字节流不会有问题

于 2016-11-28T19:12:07.587 回答
0

我也有点晚了,但是这个解决方案与 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部分......)

注意:如果它不是结构的最后一个字段,您将不会收到错误。在包含这个结构的对象中使用它时要特别小心,因为它可能会无意中添加另一个字段并得到一些奇怪的错误。例如,我不建议使用本身包含 a 的成员定义结构/类Flexible,例如这个:

class A{
  Flexible<char> a;
};

class B{
  A a;
};

因为之后很容易犯这个错误:

class B{
  A a;
  int i;
};
于 2017-11-10T22:04:52.127 回答