Изменяемая структура C ++ - PullRequest
20 голосов
/ 27 марта 2009

Это лучший способ создать структуру переменного размера в 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;
}

Редактировать: переименованные имена переменных и измененный код для большей корректности.

Ответы [ 10 ]

10 голосов
/ 27 марта 2009

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

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

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

  • Точно так же вам может потребоваться принять во внимание выравнивание и упаковку.

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

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

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

7 голосов
/ 27 марта 2009

Если вы никогда не добавляете конструктор / деструктор, операторы присваивания или виртуальные функции в вашу структуру, используя 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;
}

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

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

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

5 голосов
/ 27 марта 2009

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

Но это не очень хорошая идея для 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

3 голосов
/ 27 марта 2009

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

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

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

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

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

2 голосов
/ 27 марта 2009

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

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. Я взял некоторую свободу и изменил битовую длину на байтовую длину. Если этот класс представляет сетевой пакет, вам будет гораздо лучше иметь дело с байтами, а не с битами (по моему мнению). Массив данных представляет собой массив без знака char, а не без знака int. Опять же, это основано на моем предположении, что этот класс представляет сетевой пакет. Конструктор позволяет создать Пакет следующим образом:

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

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

1 голос
/ 27 марта 2009

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

К счастью, библиотека наддува выполнила большую часть сложной задачи:

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));
}
1 голос
/ 27 марта 2009

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

0 голосов
/ 27 марта 2009

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

0 голосов
/ 27 марта 2009

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

...