Variable Sized Struct C ++

Это лучший способ сделать структуру с переменным размером в C ++? Я не хочу использовать вектор, потому что после инициализации длина не изменяется.

struct Packet { unsigned int bytelength; unsigned int data[]; }; Packet* CreatePacket(unsigned int length) { Packet *output = (Packet*) malloc((length+1)*sizeof(unsigned int)); output->bytelength = length; return output; } 

Изменить: переименовали имена переменных и изменили код, чтобы быть более правильными.

Некоторые мысли о том, что вы делаете:

  • Использование идиумы struct length длины C-стиля позволяет вам выполнить одно бесплатное распределение хранилища на один пакет, что вдвое меньше, чем требуется, если struct Packet содержит std::vector . Если вы выделяете очень большое количество пакетов, то выполнение половины бесплатных распределений / освобождений от хранения может быть очень значительным. Если вы также выполняете сетевой доступ, то время, ожидаемое в сети, вероятно, будет более значительным.

  • Эта структура представляет собой пакет. Планируете ли вы читать / писать из сокета напрямую в struct Packet ? Если это так, вам, вероятно, нужно рассмотреть порядок байтов. Вам придется конвертировать из хоста в сетевой порядок байтов при отправке пакетов и наоборот при получении пакетов? Если это так, то вы можете байт-обменивать данные в своей структуре переменной длины. Если вы конвертируете это для использования вектора, имеет смысл написать методы для сериализации / десериализации пакета. Эти методы передавали бы его в / из смежного буфера, принимая во внимание порядок байтов.

  • Аналогичным образом, вам может потребоваться согласование и упаковка.

  • Вы никогда не можете подclassифицировать Packet . Если вы это сделали, переменные-члены подclassа будут перекрываться с массивом.

  • Вместо malloc и free вы можете использовать Packet* p = ::operator new(size) и ::operator delete(p) , поскольку struct Packet является POD-типом и в настоящее время не имеет преимущества от его конструктора по умолчанию и его деструктора, называемого , Потенциальная польза от этого заключается в том, что глобальный operator new обрабатывает ошибки с использованием глобального нового обработчика и / или исключений, если это имеет для вас значение.

  • Возможно, что идиома struct length length с новыми и удаленными операциями работает, но не очень хорошо. Вы можете создать пользовательский operator new который занимает длину массива, реализуя static void* operator new(size_t size, unsigned int bitlength) , но вам все равно придется установить переменную члена битллона. Если вы сделали это с помощью конструктора, вы можете использовать слегка избыточное выражение Packet* p = new(len) Packet(len) для выделения пакета. Единственное преимущество, которое я вижу по сравнению с использованием глобального operator new и operator delete , заключается в том, что клиенты вашего кода могут просто вызвать delete p вместо ::operator delete(p) . Обтекание выделения / освобождения в отдельных функциях (вместо прямого вызова delete p ) прекрасно, если они правильно вызываются.

Если вы никогда не добавляете конструктор / деструктор, операторы присваивания или виртуальные функции в вашу структуру с использованием malloc / free для распределения безопасны.

Он смущен в кругах c ++, но я считаю, что использование его в порядке, если вы задокументируете его в коде.

Некоторые комментарии к вашему коду:

 struct Packet { unsigned int bitlength; unsigned int data[]; }; 

Если я помню, что правильное объявление массива без длины является нестандартным. Он работает на большинстве компиляторов, но может дать вам предупреждение. Если вы хотите быть совместимым, объявите массив длиной 1.

 Packet* CreatePacket(unsigned int length) { Packet *output = (Packet*) malloc((length+1)*sizeof(unsigned int)); output->bitlength = length; return output; } 

Это работает, но вы не учитываете размер структуры. Код будет разбит после добавления новых членов в вашу структуру. Лучше сделайте это так:

 Packet* CreatePacket(unsigned int length) { size_t s = sizeof (Packed) - sizeof (Packed.data); Packet *output = (Packet*) malloc(s + length * sizeof(unsigned int)); output->bitlength = length; return output; } 

И напишите комментарий в определении структуры пакета, что данные должны быть последним членом.

Btw – распределение структуры и данных с одним распределением – это хорошо. Вы вдвое уменьшаете количество распределений, а также улучшаете локальность данных. Это может значительно улучшить производительность, если вы выделите множество пакетов.

К сожалению, c ++ не обеспечивает хороший механизм для этого, поэтому вы часто получаете такие malloc / free hacks в приложениях реального мира.

Это нормально (и это была стандартная практика для C).

Но это не очень хорошая идея для C ++.
Это связано с тем, что компилятор автоматически генерирует целый набор других методов для вас вокруг classа. Эти методы не понимают, что вы обманули.

Например:

 void copyRHSToLeft(Packet& lhs,Packet& rhs) { lhs = rhs; // The compiler generated code for assignement kicks in here. // Are your objects going to cope correctly?? } Packet* a = CreatePacket(3); Packet* b = CreatePacket(5); copyRHSToLeft(*a,*b); 

Используйте std :: vector <>, это намного безопаснее и работает правильно.
Я бы также поспорил, что он так же эффективен, как и ваша реализация после того, как оптимизатор начинает играть.

В качестве альтернативы boost содержит массив фиксированного размера:
http://www.boost.org/doc/libs/1_38_0/doc/html/array.html

Вы можете использовать метод «C», если хотите, но для безопасности сделайте так, чтобы компилятор не пытался его скопировать:

 struct Packet { unsigned int bytelength; unsigned int data[]; private: // Will cause compiler error if you misuse this struct void Packet(const Packet&); void operator=(const Packet&); }; 

Если вы действительно выполняете C ++, нет никакой практической разницы между classом и структурой, кроме видимости элемента по умолчанию – classы по умолчанию имеют конфиденциальную видимость, а структуры имеют общедоступную видимость по умолчанию. Следующие эквиваленты:

 struct PacketStruct { unsigned int bitlength; unsigned int data[]; }; class PacketClass { public: unsigned int bitlength; unsigned int data[]; }; 

Дело в том, что вам не нужен CreatePacket (). Вы можете просто инициализировать объект struct конструктором.

 struct Packet { unsigned long bytelength; unsigned char data[]; Packet(unsigned long length = 256) // default constructor replaces CreatePacket() : bytelength(length), data(new unsigned char[length]) { } ~Packet() // destructor to avoid memory leak { delete [] data; } }; 

Несколько замечаний. В C ++ используйте new вместо malloc. Я взял немного свободы и изменил длину битлин до байта. Если этот class представляет сетевой пакет, вам будет намного лучше иметь дело с байтами вместо битов (на мой взгляд). Массив данных представляет собой массив unsigned char, а не unsigned int. Опять же, это основано на моем предположении, что этот class представляет сетевой пакет. Конструктор позволяет создать пакет так:

 Packet p; // default packet with 256-byte data array Packet p(1024); // packet with 1024-byte data array 

Деструктор вызывается автоматически, когда экземпляр пакета выходит из области действия и предотвращает утечку памяти.

Я, вероятно, просто придерживаюсь использования vector<> если минимальные дополнительные накладные расходы (возможно, одно дополнительное слово или указатель на вашу реализацию) действительно создают проблему. Ничто не говорит о том, что вы должны resize () вектора после его создания.

Тем не менее, существует несколько преимуществ перехода с vector<> :

  • он уже правильно обрабатывает копирование, назначение и уничтожение – если вы откатываете свой собственный, вам нужно убедиться, что вы правильно их обрабатываете
  • вся поддержка iteratorа есть – опять же, вам не нужно сворачивать свои собственные.
  • все уже знают, как его использовать

Если вы действительно хотите предотвратить наращивание массива после его создания, вы можете захотеть иметь собственный class, который наследует от vector<> конфиденциально или имеет vector<> элемент vector<> и только выставлять через методы, которые просто обрабатывают векторные методы этими битами вектор, который вы хотите, чтобы клиенты могли использовать. Это должно помочь вам быстро перейти с довольно хорошей уверенностью в утечке, а что нет. Если вы это сделаете и обнаружите, что небольшие накладные расходы на вектор не работают на вас, вы можете переопределить этот class без помощи вектора, и ваш код клиента не должен изменяться.

Вероятно, вам нужно что-то легче, чем вектор для высоких выступлений. Вы также хотите быть очень конкретным относительно размера вашего пакета, чтобы быть кросс-платформенным. Но вы также не хотите беспокоиться о утечке памяти.

К счастью, библиотека boost сделала большую часть тяжелой части:

 struct packet { boost::uint32_t _size; boost::scoped_array _data; packet() : _size(0) {} explicit packet(packet boost::uint32_t s) : _size(s), _data(new unsigned char [s]) {} explicit packet(const void * const d, boost::uint32_t s) : _size(s), _data(new unsigned char [s]) { std::memcpy(_data, static_cast(d), _size); } }; typedef boost::shared_ptr packet_ptr; packet_ptr build_packet(const void const * data, boost::uint32_t s) { return packet_ptr(new packet(data, s)); } 

Здесь уже много хороших мыслей. Но один отсутствует. Гибкие массивы являются частью C99 и, следовательно, не являются частью C ++, хотя некоторые компиляторы C ++ могут предоставить эту функциональность, для этого нет никакой гарантии. Если вы найдете способ использовать их на C ++ приемлемым образом, но у вас есть компилятор, который его не поддерживает, возможно, вы можете отказаться от «classического» способа

Вы должны объявить указатель, а не массив с неопределенной длиной.

Нет ничего плохого в использовании вектора для массивов неизвестного размера, которые будут исправлены после инициализации. ИМХО, это именно то, для чего нужны векторы. После инициализации вы можете притворяться, что вещь является массивом, и она должна вести себя одинаково (включая поведение времени).

Interesting Posts

Как найти MAC-адрес устройства Android программно

Как я могу повысить производительность с новым предварительным просмотром Visual Studio 2013?

Как трафик Firefox не отображается в Fiddler?

Есть ли команда в Windows, например ps -aux в UNIX?

Не занимайтесь деятельностью – для чего?

Изменение цвета фона для выбранного элемента ListBox

Преобразование данных из длинного формата в широкоформатный с несколькими столбцами измерения

Преобразование строк в дату mm / dd / yy до YYYY-MM-DD в java

Что такое zip (функциональное программирование?)

Невозможно создать таблицу базы данных с именем «пользователь» в PostgreSQL

Как использовать ScrollViewer.ScrollToVerticalOffset?

Переменная среды WINDIR разрешает% SystemRoot%, а не C: \ windows

Восстановление системы удаляет обновления Windows?

Как вставить шрифт с помощью моего приложения на C #? (с использованием Visual Studio 2005)

Убедитесь, что сгоревший DVD будет работать с любым DVD-плеером?

Давайте будем гением компьютера.