struct Data
{
int a;
int b;
std::string c;
};
struct Base
{
int sz;
Data d[1];
};
void doSomething(Base *b)
{
for (int i = 0; i < b.sz; ++i)
{
std::cout << b->d[i].a << b->d[i].s << '\n'; /// *************
}
}
struct Der11
{
Base b;
Data d[10];
};
void func()
{
Der10 d;
d.sz = 11;
doSomething(&d.b);
}
Можно ли по стандарту так делать? А если Data будет POD? Должна ли Base быть POD? A Der11? А если в Base будет еще какой-нить double — не будет ли проблем с выравниванием?
E>struct Data {};
E>struct Base
E>{
E> int sz;
E> Data d[1];
E>};
E>struct Der11
E>{
E> Base b;
E> Data d[10];
E>};
E>
E>Можно ли по стандарту так делать? А если Data будет POD? Должна ли Base быть POD? A Der11? А если в Base будет еще какой-нить double — не будет ли проблем с выравниванием?
О боже, какие страшные хаки. Вот только недавно встретил в WINAPI такое же, полчаса думал что это и как они до такого додумались. Вот откуда оказывается ноги то расли
Если вам хочется задавать кол-во элементов в зависимости от класса наследника, вот вам несколько путей:
Здравствуйте, johny5, Вы писали:
J>Если вам хочется задавать кол-во элементов в зависимости от класса наследника, вот вам несколько путей:
J>Динамический:
J>
J>struct Base
J>{
J> std::vector<Data> d;
J>};
J>
У меня не везде есть stl и даже куча
Можно сделать так (у меня счас так и сделано):
struct Base
{
Data *d;
int sz;
};
struct Der11
{
Der11() { d = dt; sz = 11; }
Data dt[11];
};
но захотелось сэкономить указатель
J>Статический:
J>
J>template<int N>
J>struct Base
J>{
J> Data d[N];
J>};
J>struct Der10 : public Base<10>
J>{};
J>template<int N>
J>void doSomething(Base<N> *b)
J>
doSomething становится шаблонной — т.е. должна жить в хидере. Одна может быть большой, она может дергать другие методы, она может иметь много зависимостей, которые тоже придется вытянуть в хидер — в общем делать base шаблонным не хочется.
Можно как-то вот так извернуться:
struct OtherBaseData
{
int a, b, c;
};
template<int N>
struct Base
{
OtherBaseData obd;
Data d[N];
};
void int_doSomething(OtherBaseData *obd, Data *d, int d_sz);
template<int N>
void doSomething(Base<N> *b) { int_doSomething(&b->obd, b->d, N); }
Но если Base помимо массива содержит еще данные, то придется с ними приседать дополнительно.
Здравствуйте, enji, Вы писали:
E>У меня не везде есть stl и даже куча E>но захотелось сэкономить указатель
Увидел в первоначальном примере std::string, предположил что нормальный С++, но если С++ у вас коцанный, сгодиться и такой вариант, с указателем. Если размер указателя конечно не критичен. С align-ами возиться я бы не стал просто из чуства гигиены.
Можно кстате align задать явно (#pragma pack, __declspec..), например если задать упаковку в 1 байт на обе структуры Data и Base, то в вашем первоначальном варианте "подсоединённая" Data гарантировано ляжет за первой. Впрочем выравнивание в 1 байт это достаточно серьёзный удар про производительности, по другим комбинациям может подскажет кто то ещё — тут я не знаю.
J>>Статический:
E>doSomething становится шаблонной — т.е. должна жить в хидере. Одна может быть большой, она может дергать другие методы, она может иметь много зависимостей, которые тоже придется вытянуть в хидер — в общем делать base шаблонным не хочется.
E>void int_doSomething(OtherBaseData *obd, Data *d, int d_sz);
E>template<int N> E>void doSomething(Base<N> *b) { int_doSomething(&b->obd, b->d, N); } E>[/ccode]
Либо так либо просто декларировать void doSomething(Base<N> *b); без имплементации. Имплементацию разместить в .cpp и явно инстанциировать шаблон там же, т.е.:
On 11/30/2011 09:07 AM, enji wrote:
> Можно ли по стандарту так делать?
Понятие "так можно делать по стандарту" не определено.
Можешь делать, вопрос в том, что будет после этого.
Ты можешь так делать, если сам будешь правильно выделять память
под массив переменного размера и сам будешь вызывать конструктры
всех элементов. (и деструкторы также).
На счёт Der11 -- размер Base формально определён, но фактически -- переменный.
Как ты его хочешь зафигачить в другой класс мембером (или предком) --
не понятно. Максимум что можно с ним делать -- хранить на него
ссылку (указатель или ссылку).
А если Data будет POD?
Если это POD, то соответственно конструкторы и деструкторы вызывать
не надо, потому что их просто нет.
Должна ли Base быть > POD?
Нет, см. выше.
A Der11?
Der11 вообще некорректно. Не с точки зрения С++, а просто по жизни.
См. выше.
А если в Base будет еще какой-нить double — не будет ли проблем с > выравниванием?
нет. Если правильно будешь выделять память.
На самом деле самый большой вопрос -- а занафига.
Потому что если это у тебя С++, то проще держать std::vector и не парица,
а если это С, то тогда с какого фига там в элементе std::string ?
Здравствуйте, enji, Вы писали:
E>Можно ли по стандарту так делать? А если Data будет POD? Должна ли Base быть POD? A Der11? А если в Base будет еще какой-нить double — не будет ли проблем с выравниванием?
По стандарту нет гарантии, что между Der11::b и Der11::d отсутствует padding, да и выход за границы массива уже UB, с другой стороны, на практике это должно работать. По большому счету, такой трюк ничем не отличается от "struct hack" гарантированно работающего как в gcc так и в MSVC.
Здравствуйте, enji, Вы писали:
E>Можно ли по стандарту так делать?
btw,
в стандарте языка C описаны flexible array member (см. пункт 6.7.2.1). Соответственно там есть гарантии и всё такое. При этом в некоторых С++ компиляторах flexible array member поддерживается как расширение и для С++. То есть если у вас ограниченный список используемых компиляторов, то можно просто посмотреть в документации. Или, как вариант, можно скомпилировать нужный участок кода C-компилятором (хотя конечно с std::string это сделать не получится).
Здравствуйте, MasterZiv, Вы писали:
MZ>On 11/30/2011 09:07 AM, enji wrote:
>> Можно ли по стандарту так делать?
MZ>Понятие "так можно делать по стандарту" не определено. MZ>Можешь делать, вопрос в том, что будет после этого.
MZ>Ты можешь так делать, если сам будешь правильно выделять память MZ>под массив переменного размера и сам будешь вызывать конструктры MZ>всех элементов. (и деструкторы также).
MZ>На счёт Der11 -- размер Base формально определён, но фактически -- переменный. MZ>Как ты его хочешь зафигачить в другой класс мембером (или предком) -- MZ>не понятно. Максимум что можно с ним делать -- хранить на него MZ>ссылку (указатель или ссылку).
Размер Base фиксирован, а дополнительные элементы массива лежат в Der11. При этом работающие с указателем на Base, обращаясь скажем к Base::d[5], фактически обращаются к Der11::d[4].
Вопрос собственно заключался в том, не вставит ли компилятор какие-то свои поля между Der11:b и Der11::d и обязательно ли b будет предшествовать d. Как я понимаю, тут есть нюансы, в зависимости от того POD это или не POD.
MZ>На самом деле самый большой вопрос -- а занафига.
У меня С++ в условиях дефицита памяти, отсутствия кучи и stl. Была мысль немного сэкономить. Однако хочется этот код компилить и для десктопа, не заморачиваясь при этом с подавлением выравнивания и паддинга
MZ>Потому что если это у тебя С++, то проще держать std::vector и не парица, MZ>а если это С, то тогда с какого фига там в элементе std::string ?
std::string — просто как пример объекта с конструктором и деструктором. На самом деле там объект какого-то моего класса.
Здравствуйте, enji, Вы писали:
E>Здравствуйте, MasterZiv, Вы писали:
MZ>>On 11/30/2011 09:07 AM, enji wrote:
>>> Можно ли по стандарту так делать?
MZ>>Понятие "так можно делать по стандарту" не определено. MZ>>Можешь делать, вопрос в том, что будет после этого.
MZ>>Ты можешь так делать, если сам будешь правильно выделять память MZ>>под массив переменного размера и сам будешь вызывать конструктры MZ>>всех элементов. (и деструкторы также).
MZ>>На счёт Der11 -- размер Base формально определён, но фактически -- переменный. MZ>>Как ты его хочешь зафигачить в другой класс мембером (или предком) -- MZ>>не понятно. Максимум что можно с ним делать -- хранить на него MZ>>ссылку (указатель или ссылку).
E>Размер Base фиксирован, а дополнительные элементы массива лежат в Der11. При этом работающие с указателем на Base, обращаясь скажем к Base::d[5], фактически обращаются к Der11::d[4].
E>Вопрос собственно заключался в том, не вставит ли компилятор какие-то свои поля между Der11:b и Der11::d и обязательно ли b будет предшествовать d. Как я понимаю, тут есть нюансы, в зависимости от того POD это или не POD.
Стандарт не дает гарантии отсутствия padding'а между Der11::b и Der11::d, с другой стороны, можно ожидать, что требования по выравниванию для Base::d и Der11::d одинаковые и эти поля будут идти последовательно. Для большей надежности это можно проверить assert'ами времени компиляции:
проверка для C++11, но можно изобразить что-то похожее и для предыдущего стандарта.
По стандарту, расположение членов класса зависит от того является-ли класс standard-layout классом. Ответ на этот вопрос зависит от того является-ли standard-layout std::string, каких-либо гарантий на этот счет в стандарте нет, с другой стороны, можно ожидать, что любая разумная реализация будет отвечать этому требованию. Впрочем, если у тебя не std::string, а свой класс, можешь сам проверить выполнение нужных требований:
A standard-layout class is a class that:
— has no non-static data members of type non-standard-layout class (or array of such types) or reference,
— has no virtual functions (10.3) and no virtual base classes (10.1),
— has the same access control (Clause 11) for all non-static data members,
— has no non-standard-layout base classes,
— either has no non-static data members in the most derived class and at most one base class with
non-static data members, or has no base classes with non-static data members, and
— has no base classes of the same type as the first non-static data member.
On 12/01/2011 03:21 PM, enji wrote:
> Размер Base фиксирован, а дополнительные элементы массива лежат в Der11. При > этом работающие с указателем на Base, обращаясь скажем к Base::d[5], фактически > обращаются к Der11::d[4].
Ты кажется ничего не понял.
struct Der11
{
Base b;
Data d[10];
};
Размер Base не определён. Формально sizeof(Base) будет определён для
размера массива в 1 элемент. Фактически -- кол-во элементов переменное.
Base нельзя ни агрегировать, ни вкладывать в массив или вектор по значению.
Это -- структура переменной длины.
> Вопрос собственно заключался в том, не вставит ли компилятор какие-то свои поля > между Der11:b и Der11::d и обязательно ли b будет предшествовать d. Как я > понимаю, тут есть нюансы, в зависимости от того POD это или не POD.
Это дело 10е и решается.
> У меня С++ в условиях дефицита памяти, отсутствия кучи и stl. Была мысль немного > сэкономить. Однако хочется этот код компилить и для десктопа, не заморачиваясь > при этом с подавлением выравнивания и паддинга
Легче будет туда закинуть какой-то из STL, какую-то реализацию.
Я не верю, что это невозможно.
Хотя конечно могут быть другие причины для таких решениий.
Здравствуйте, MasterZiv, Вы писали:
MZ>On 12/01/2011 03:21 PM, enji wrote:
>> Размер Base фиксирован, а дополнительные элементы массива лежат в Der11. При >> этом работающие с указателем на Base, обращаясь скажем к Base::d[5], фактически >> обращаются к Der11::d[4].
MZ>Ты кажется ничего не понял.
MZ>struct Der11 MZ>{ MZ> Base b; MZ> Data d[10]; MZ>};
MZ>Размер Base не определён. Формально sizeof(Base) будет определён для MZ>размера массива в 1 элемент. Фактически -- кол-во элементов переменное. MZ>Base нельзя ни агрегировать, ни вкладывать в массив или вектор по значению. MZ>Это -- структура переменной длины.
struct Base
{
int sz;
Data d[1];
};
С какой стати это структура переменной длины? Размер хоть и зависит от реализации, всё равно, фиксированный, и никаких проблем с агрегацией нет.
Здравствуйте, MasterZiv, Вы писали:
MZ>Ты кажется ничего не понял.
MZ>struct Der11 MZ>{ MZ> Base b; MZ> Data d[10]; MZ>};
MZ>Размер Base не определён. Формально sizeof(Base) будет определён для MZ>размера массива в 1 элемент. Фактически -- кол-во элементов переменное. MZ>Base нельзя ни агрегировать, ни вкладывать в массив или вектор по значению. MZ>Это -- структура переменной длины.
Это все так. Однако Der11 — это как раз способ создать Base из 11 элементов. Это не некая самостоятельная структура, просто аналог malloc(sizeof(Base) + sizeof(Data) * 10) на стеке. Все остальные части программы работают с Base по указателю\ссылке, а про Der11 не знают.
>> Вопрос собственно заключался в том, не вставит ли компилятор какие-то свои поля >> между Der11:b и Der11::d и обязательно ли b будет предшествовать d. Как я >> понимаю, тут есть нюансы, в зависимости от того POD это или не POD.
MZ>Это дело 10е и решается.
Дык вопрос как раз об этом — можно ли это просто и кроссплатформенно решить? Если да — то как, какие требования к структурам-участникам?
>> У меня С++ в условиях дефицита памяти, отсутствия кучи и stl. Была мысль немного >> сэкономить. Однако хочется этот код компилить и для десктопа, не заморачиваясь >> при этом с подавлением выравнивания и паддинга
MZ>Легче будет туда закинуть какой-то из STL, какую-то реализацию.
Ты смеешься. Я иногда каждый байт ОЗУ считаю, а ты мне предлагаешь кучу организовать и STL заюзать
Собственно вопрос возник, когда я подумал, а нельзя ли безопасно сэкономить 2 байта на указателе
MZ>Я не верю, что это невозможно.
Возможно конечно, просто надо менять железо. Однако так как поддержку старого железа никто не отменял, то ближайшие лет 10 еще придется мучаться с имеющимся
On 12/02/2011 05:09 PM, enji wrote:
> Дык вопрос как раз об этом — можно ли это просто и кроссплатформенно решить? > Если да — то как, какие требования к структурам-участникам?
Выравнивание -- штука пратформозависимая, как она может решаться кроссплатформенно ?
Пишеш #ifdef -ы и несколько вариантов кода.
егче будет туда закинуть какой-то из STL, какую-то реализацию. > > Ты смеешься. Я иногда каждый байт ОЗУ считаю, а ты мне предлагаешь кучу > организовать и STL заюзать > Собственно вопрос возник, когда я подумал, а нельзя ли безопасно сэкономить 2 > байта на указателе
MZ>Выравнивание -- штука пратформозависимая, как она может решаться кроссплатформенно ? MZ>Пишеш #ifdef -ы и несколько вариантов кода.
k.o. выше порекомендовал статические проверки к примеру. Для ПОД, как я понимаю, стандарт что-то гарантирует.
#ifdef конечно решение, но если их много, становится страшно
MZ>Так STL -- не значит "много памяти".
STL означает кучу. Если (как в моем случае) перечень живущих объектов и предельные размеры буферов известны, то глобальные объекты и стек всяко выгоднее кучи.
Кроме того, стл — это библиотека общего назначения, в каждом конкретном случае, если делать руками, можно сэкономить. Например, если знать что количество элементов в конкретном массиве — заведомо не более 100, то вот уже 3 байта экономии на размере.
E>>Например, если знать что количество элементов в конкретном массиве — заведомо не более 100, то вот уже 3 байта экономии на размере.
J>И потеря 1кб++ байт на неиспользуемых элементах. Кроме того нужно ещё знать где заканчиваются "нужные" элементы.
E>>>Например, если знать что количество элементов в конкретном массиве — заведомо не более 100, то вот уже 3 байта экономии на размере.
J>>И потеря 1кб++ байт на неиспользуемых элементах. Кроме того нужно ещё знать где заканчиваются "нужные" элементы.
E>это ты о чем?
Ну я всмысле что "не более 100" означает 100 и менее. Это стандартная Си практика, создавать статический массив с размеров под макисмально возможное кол-вом элементов в нём. Хотя при динамической природе данных, такие массивы редко бывают заполнены даже на половину. А это и есть чистая растрата памяти.
E>>>>Например, если знать что количество элементов в конкретном массиве — заведомо не более 100, то вот уже 3 байта экономии на размере.
J>>>И потеря 1кб++ байт на неиспользуемых элементах. Кроме того нужно ещё знать где заканчиваются "нужные" элементы.
E>>это ты о чем?
J>Ну я всмысле что "не более 100" означает 100 и менее. Это стандартная Си практика, создавать статический массив с размеров под макисмально возможное кол-вом элементов в нём. Хотя при динамической природе данных, такие массивы редко бывают заполнены даже на половину. А это и есть чистая растрата памяти.
Это конечно да, однако если есть задача, в которой известны предельные размеры каждого массива, то много проще создать их статически. При этом по крайней мере компилятор сможет проверить достаточность ОЗУ и не будет ошибок с нехваткой памяти, которые кстати могут проявляться лишь при определенном (не найденном при тестировании) стечении обстоятельств.