Подтвердить что ты не робот

Variable Sized Struct С++

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

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;
}

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

4b9b3361

Ответ 1

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

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

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

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

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

  • Вместо 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) отлично, если они правильно вызываются.

Ответ 2

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

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

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

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 - распределение структуры и данных с одним распределением - это хорошо. Вы вдвое уменьшаете количество распределений, а также улучшаете локальность данных. Это может немного улучшить производительность, если вы выделяете много пакетов.

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

Ответ 3

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

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

Пример:

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

Ответ 4

Вы можете использовать метод "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&);
};

Ответ 5

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

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;
    }
};

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

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

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

Ответ 6

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

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

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

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

Ответ 7

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

Ответ 8

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

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

struct packet
{
   boost::uint32_t _size;
   boost::scoped_array<unsigned char> _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<const unsigned char * const>(d), _size);
   }
};

typedef boost::shared_ptr<packet> packet_ptr;

packet_ptr build_packet(const void const * data, boost::uint32_t s)
{

    return packet_ptr(new packet(data, s));
}

Ответ 9

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

Ответ 10

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