使用模板和基类实现灵活的数组成员
Implementing flexible array members with templates and base class
在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
作为标准布局,这不是很好。
这样做的好处是,通过使用FooBase*
和调用在var2
上操作的方法,非模板化函数可以在不进行转换的情况下接收任何Foo*
,并且内存都是连续的(这可能很有用)。
有没有更好的方法来实现这一点(即合法的C++/C++11/C++14)?
我对这两个琐碎的解决方案不感兴趣(包括基类中指向数组开头的额外指针,以及在堆上分配数组)。
您想做的事情在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
,存储int
和double
以及std::string
的N
副本需要多大的缓冲区,所有内容都正确对齐。这并不容易。
一旦您完成了这项工作,您还需要编写构建上述所有内容的代码。如果你想变得有趣,你甚至可以公开一个完美的转发构造函数和构造函数包装器,允许在非默认状态下构建对象。
最后,编写一个接口,根据我注入上述tuple
s、reinterpret_cast
s的标签,查找构造对象的内存偏移量,将原始内存转换为对数据类型的引用,并返回该引用(在常量和非常量版本中)。
对于末尾的数组,您将返回一些临时数据结构,该结构重载了生成引用的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++中的要求,在C++中,存储的数据类型不必是标准布局。
通过一点类型转换,您也可以在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);
不过,我并不真的建议将其用于一般用途。(对我来说)使用这样的方案的唯一可接受的地方是以某种方式传输结构(网络或文件)。然后,我建议将它封送至具有可变长度数据的std::vector
的"适当"C++对象。
您想要做的事情在C++中根本不可能实现。原因是sizeof(T)是编译时常数,所以在类型中放置数组会使其具有编译时大小。因此,正确的c++方法可以将数组保持在类型之外。请注意,只有当数组位于某个类型内部时,才有可能将其放置到堆栈中。所以所有基于堆栈的东西都受限于数组的编译时大小。(alloca可能会解决这个问题)。您最初的C版本也有类似的问题,即类型无法处理运行时大小的数组。
这也是C++中处理可变长度数组的方法。不受支持,因为它破坏了sizeof,c++类依赖sizeof进行数据成员访问。任何不能与c++类一起使用的解决方案都是不好的。std::vector没有这样的问题。
请注意,c++11中的constexpr使自定义数据类型中的偏移量计算变得相当简单——编译时间限制仍然存在。
我知道我来晚了,但我的建议是:
template<size_t N>
struct Foo {
int var1;
std::array<int,N> var2;
};
std::array
将数据存储为int v[N];
(不在堆中),因此将其转换为字节流不会有问题
我也有点晚了,但此解决方案与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
部分之外,没有任何理由不将其作为标准…)
注意:如果它不是结构的最后一个字段,则不会出现错误。在包含此结构作为sub-sub的对象中使用this时要特别小心-子成员,因为你可能会无意中在后面添加另一个字段,并得到一些奇怪的错误。例如,我不建议定义一个成员本身包含Flexible
的结构/类,比如这个:
class A{
Flexible<char> a;
};
class B{
A a;
};
因为在之后很容易犯这个错误
class B{
A a;
int i;
};
- 为什么我无法访问指向数组中成员函数的指针?
- 如何将结构/联合数组的成员传递到函数中
- 使用带有参数包的数组的成员数组初始化类
- 有没有办法给数组的成员指定不同的名称?
- 使用包含结构的数组的成员函数
- C++:释放动态数组(结构成员)和指向此结构的指针的方法
- C 用数组作为成员创建对象
- 用另一个 constexpr 数组对成员数组进行大括号初始化
- 来自数组的成员和比特菲尔德的结构
- 如何创建指向数组类成员的指针?
- 将数组的成员设置为零
- "Empty"数组\向量成员 C++
- 功能指针数组(包括成员功能)投掷模板专业化错误
- 另一个结构内部的结构数组的成员变量没有通过引用传递
- 正确转换 2D 数组的成员变量
- 常量数组类成员初始化
- 为具有对象或对象数组作为成员的类运行不同的代码
- C++ 如何指向数组的成员
- 将包含数组数据成员的结构保存到文件
- 数组类成员初始化(C++)