在 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
如何获取一 block 内存并将其重新格式化为非常类似于数组的东西,然后重载运算符以使自己看起来像数组一样,您可以执行相同的操作.
将通过访问器访问您的数据。您将在缓冲区中手动构建您的成员。
您可以从“标签”和数据类型对列表开始。
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
,你需要多大的缓冲区来存储 int
和 double
后跟 N
个 std::string
拷贝,一切都正确对齐。这并不容易。
完成后,您还需要编写代码来构建上述所有内容。如果您想要花哨一些,您甚至可以公开一个完美的转发构造函数和构造函数包装器,从而允许在非默认状态下构造对象。
最后,编写一个接口(interface),根据我注入(inject)到上述tuple
中的标记找到构造对象的内存偏移量,reinterpret_cast
将原始内存放入对数据类型的引用,并返回该引用(在 const 和非 const 版本中)。
对于末尾的数组,您将返回一些临时数据结构,该结构重载了生成引用的 operator[]
。
如果您看一下 std::vector
如何将内存块转换为数组,并将其与 boost::mpl
安排标记到数据的方式相结合 map ,然后还手动将事物弄乱以保持正确对齐,每一步都具有挑战性,但并非不可能。我在这里使用的凌乱语法也可以改进(在某种程度上)。
结束界面可能是
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)“任意长度数组在结束”。甚至可以在中间注入(inject)任意长度的数组(但这意味着找到该数组后面的结构成员的地址是一个运行时问题:但是,因为我们的 operator^
被允许运行任意代码,并且您的结构可以存储数组的长度,我们能够做到这一点)。
但是,我想不出一种更简单、可移植的方式来完成您在 C++ 中提出的要求,其中存储的数据类型不必是标准布局。
关于c++ - 使用模板和基类实现灵活的数组成员,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17424731/