В 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;
Затем вам нужно будет написать шаблон mojo, который выяснит, учитывая N
во время выполнения, какой большой буфер вам нужно будет хранить int
а также double
с последующим N
копии std::string
, все правильно выровнено. Это не легко.
Как только вы это сделаете, вам также нужно будет написать код, который создает все, что описано выше. Если вы хотите получить фантазию, вы бы даже представили идеальный конструктор пересылки и оболочки конструктора, позволяющие создавать объекты в состоянии не по умолчанию.
Наконец, напишите интерфейс, который находит смещение памяти построенных объектов на основе тегов, которые я вставил в вышеупомянутые tuple
s, reinterpret_cast
Сохраняет необработанную память в ссылку на тип данных и возвращает эту ссылку (как в константной, так и в неконстантной версиях).
Для массива в конце вы должны вернуть некоторую временную структуру данных, которая была перегружена 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 :: vector таких проблем нет.
Обратите внимание, что constexpr в c ++ 11 значительно упрощает вычисление смещения в ваших пользовательских типах данных — ограничение времени компиляции все еще существует.
Я знаю, что я немного опаздываю, но мое предложение будет таким:
template<size_t N>
struct Foo {
int var1;
std::array<int,N> var2;
};
std::array
хранит данные как int v[N];
(не в куче), поэтому не было бы проблем с преобразованием его в потоки байтов
Я тоже немного опоздал, но это решение совместимо с гибкими массивами Си (если вы, конечно, играете с препроцессором):
#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 -…- sub-member, потому что вы можете непреднамеренно добавить другое поле после и получить некоторые странные ошибки. Например, я бы не советовал определять структуру / класс с членом, который сам содержит Flexible
, такой как этот:
class A{
Flexible<char> a;
};
class B{
A a;
};
Потому что легко сделать эту ошибку после:
class B{
A a;
int i;
};