在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
作为标准布局,这不是很漂亮。< / p>
这样做的好处是,非模板化函数可以通过Foo*
并调用在FooBase*
上运行的方法来接收任何var2
而不进行转换,并且内存都是连续的(这可能很有用)。
有没有更好的方法来实现这一点(这是合法的C ++ / C ++ 11 / C ++ 14)?
我对两个简单的解决方案不感兴趣(包括基类中的一个额外指针到数组的开头,并在堆上分配数组)。
答案 0 :(得分:5)
在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;
然后你必须写一些模板mojo,在运行时给出N
,你需要多大的缓冲区才能存储int
和double
然后是N
std::string
个副本,所有内容都正确对齐。这并不容易。
完成后,您还需要编写构造上述所有内容的代码。如果你想得到想象,你甚至会暴露出一个完美的转发构造函数和构造函数包装器,允许在非默认状态下构造对象。
最后,编写一个接口,根据我注入上述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 ++中所要求的事情,其中存储的数据类型不必是标准布局。
答案 1 :(得分:1)
通过一些类型转换,您也可以在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
用于可变长度数据。
答案 2 :(得分:0)
在C ++中根本不可能做什么。原因是sizeof(T)是编译时常量,因此将数组放在类型中会使其具有编译时大小。所以正确的c ++方式可以使数组保持在类型之外。请注意,只有在某种类型的内部才能将数组放入堆栈。因此,基于堆栈的所有内容都限于数组的编译时大小。 (alloca可能会解决这个问题)。您的原始C版本也有类似的问题,类型无法处理运行时大小的数组。
这也是C ++中可变长度数组的处理。不支持,因为它打破了sizeof,c ++类依赖sizeof进行数据成员访问。任何不能与c ++类一起使用的解决方案都不好。 std :: vector没有这样的问题。
请注意,c ++ 11中的constexpr使自定义数据类型中的偏移量计算变得相当简单 - 编译时限制仍然存在。
答案 3 :(得分:0)
我知道我有点迟到了,但我的消息是:
template<size_t N>
struct Foo {
int var1;
std::array<int,N> var2;
};
std::array
将数据存储为int v[N];
(不在堆中),因此将其转换为字节流不会有问题
答案 4 :(得分: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
部分之外没有理由它不会是标准的......)
注意:如果它不是结构的最后一个字段,则不会出现错误。特别注意在包含此结构的对象中使用它作为sub-sub -...-子成员,因为您可能会无意中添加另一个字段并获得一些奇怪的错误。例如,我不建议使用一个本身包含Flexible
的成员来定义结构/类,例如:
class A{
Flexible<char> a;
};
class B{
A a;
};
因为在以下情况之后很容易犯这个错误:
class B{
A a;
int i;
};