在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
不是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
后跟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 ++,这里的数据类型存储的数据并不必须是标准布局中问什么。
随着一点点的类型转换,您可以使用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
的可变长数据。
你想要做什么是根本不可能在C ++中。 其原因是,的sizeof(T)是编译时间常数,这样,将一个阵列内的类型使其具有编译时的大小。 所以,适当的C ++做这件事的方式保持的类型以外的数组。 请注意,将阵列堆栈是唯一可能的,如果它是某种类型的内部。 所以一切都基于堆栈被限制在编译时数组的大小。 (ALLOCA可能会解决这个问题)。 你原来的C版本也有类似的问题,这类型不能处理运行时大小的数组。
这也是处理在C ++中的可变长度阵列。 不支持,因为它打破的sizeof和C ++类依赖的sizeof数据成员的访问。 不能使用C ++类一起使用的任何解决方案是没有好处的。 的std ::矢量有没有这样的问题。
注意在C ++ 11 constexpr使得偏移计算在您的自定义数据类型相当简单 - 编译时的限制仍然存在。
我知道我有点晚了这里,但我sugestion将是:
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
部分...)
注意:如果不是该结构的最后一个字段,你不会得到一个错误。 要格外谨慎,在装有该结构作为子子-...-子成员对象用这个,因为你可能无意中后添加其他领域,并得到一些莫名其妙的错误。 例如,我不会建议限定与成员的结构/类其本身包含一个Flexible
,像这样的:
class A{
Flexible<char> a;
};
class B{
A a;
};
因为它很容易后做这个错误:
class B{
A a;
int i;
};