Variable Sized Struct C ++

stemmer
18

Er dette den beste måten å gjøre en variabel størrelse struct i C ++? Jeg ønsker ikke å bruke vektor fordi lengden ikke endres etter initialisering.

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

Edit: omdøpt variabelnavn og endrede kode for å være mer korrekt.

Publisert på 27/03/2009 klokken 03:48
kilden bruker
På andre språk...                            


10 svar

stemmer
9

Noen tanker om hva du gjør:

  • Bruke C-stil variabel lengde struct idiom kan du utføre en gratis butikk tildeling per pakke, som er halvparten så mange som ville være nødvendig dersom struct Packetinneholdt en std::vector. Hvis du er tildeling av et svært stort antall pakker, deretter utføre halvparten så mange gratis store bevilgninger / deallocations kan meget vel være betydelig. Hvis du også gjør nettverk åpner, så tidsbruk venter for nettverket vil trolig være mer betydelig.

  • Denne konstruksjon utgjør en pakke. Planlegger du å lese / skrive fra en stikkontakt direkte inn i en struct Packet? Hvis ja, må du sannsynligvis til å vurdere byte rekkefølge. Er du nødt til å konvertere fra maskin til nettverket byte rekkefølge ved sending av pakker, og vice versa når du mottar pakker? Hvis ja, så du kan byte-swap dataene på plass i variabel lengde struct. Hvis du har konvertert dette til å bruke en vektor, ville det være fornuftig å skrive metoder for seriefunksjon / deserializing pakken. Disse metodene vil overføre den til / fra en sammenhengende buffer, idet det byte rekkefølge i betraktning.

  • På samme måte må du kanskje ta justering og pakking i betraktning.

  • Du kan aldri underklasse Packet. Hvis du gjorde det, må underklassen medlemsvariabler vil overlappe med array.

  • I stedet for mallocog freekunne bruke Packet* p = ::operator new(size)og ::operator delete(p)da struct Packeter en POD type og for øyeblikket ikke nytte av å ha sin standardkonstruktør og dens destructor kalles. Den (potensielle) Fordelen med å gjøre det er at de globale operator newhåndterer feil å bruke den globale ny-behandleren og / eller unntak, hvis det betyr noe for deg.

  • Det er mulig å gjøre med variabel lengde struct idiom arbeidet med den nye og slette operatører, men ikke godt. Du kan lage en tilpasset operator newsom tar en rekke lengde ved å implementere static void* operator new(size_t size, unsigned int bitlength), men du ville fortsatt å sette bitlength medlem variabel. Hvis du gjorde dette med en konstruktør, kan du bruke litt overflødig uttrykk Packet* p = new(len) Packet(len)for å tildele en pakke. Den eneste fordelen jeg ser i forhold til å bruke global operator newog operator deletevil være at kunder av koden kan bare ringe delete pi stedet for ::operator delete(p). Innpakning tildeling / fradeling i separate funksjoner (i stedet for å ringe delete pdirekte) er greit så lenge de får kalles riktig.

Svarte 27/03/2009 kl. 05:15
kilden bruker

stemmer
6

Hvis du aldri legge en konstruktør / destructor, oppdrag operatører eller virtuelle funksjoner i strukturen ved hjelp av malloc / gratis for tildeling er trygt.

Det er mislikt i C ++ sirkler, men jeg anser bruken av det greit hvis du dokumentere det i koden.

Noen kommentarer til koden:

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

Hvis jeg husker riktig å erklære en matrise uten en lengde er ikke-standard. Det fungerer på de fleste kompilatorer, men kan gi deg en advarsel. Hvis du ønsker å være kompatibel erklære din rekke lengde 1.

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

Dette fungerer, men du trenger ikke ta størrelsen på strukturen i betraktning. Koden vil bryte når du legger til nye medlemmer i strukturen. Bedre gjøre det på denne måten:

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

Og skrive en kommentar i din pakke struktur definisjon at data må være det siste medlemmet.

Forresten - tildeling av strukturen og dataene med en enkelt allokering er bra. Du halvere antall tildelinger på den måten, og du forbedre lokaliteten av data også. Dette kan forbedre ytelsen ganske mye hvis du fordele mange pakker.

Dessverre c ++ ikke gir en god mekanisme for å gjøre dette, slik at du ofte ender opp med en slik malloc / gratis hacks i virkelige verden programmer.

Svarte 27/03/2009 kl. 04:03
kilden bruker

stemmer
5

Dette er OK (og var standard praksis for C).

Men dette er ikke en god idé for C ++.
Dette er fordi kompilatoren genererer et helt sett av andre metoder automatisk for deg rundt i klassen. Disse metodene forstår ikke at du har jukset.

For eksempel:

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

Bruk std :: vector <> det er mye tryggere og fungerer riktig.
Jeg vil også satse det er like effektiv som implementeringen etter optimizer spark i.

Alternativt boost inneholder en fast størrelse matrise:
http://www.boost.org/doc/libs/1_38_0/doc/html/array.html

Svarte 27/03/2009 kl. 04:38
kilden bruker

stemmer
3

Du kan bruke "C" metoden hvis du vil, men for sikkerhet gjør det så kompilatoren ikke vil prøve å kopiere det:

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&);
};
Svarte 27/03/2009 kl. 14:03
kilden bruker

stemmer
2

Jeg vil nok bare holde med å bruke en vector<>mindre minimal ekstra overhead (sannsynligvis en eneste ekstra ord eller pekeren over implementeringen) er virkelig poserer et problem. Det er ingenting som sier at du må endre størrelsen () en vektor når den er blitt bygget.

Men det er flere Fordelene med å gå med vector<>:

  • den håndterer allerede kopi, oppdrag og ødeleggelse riktig - hvis du rulle din egen du trenger for å sikre at du håndtere disse på riktig måte
  • all iterator støtte er det - igjen, trenger du ikke å rulle din egen.
  • alle vet allerede hvordan du bruker den

Hvis du virkelig ønsker å hindre rekken fra å vokse når bygget, vil du kanskje vurdere å ha din egen klasse som arver fra vector<>privat eller har et vector<>medlem, og bare utsetter via metoder som bare thunk til vektor metoder de biter av vektor som du ønsker kunder å være i stand til å bruke. Det burde hjelpe deg å komme raskt i gang med ganske god sikkerhet for at lekkasjer og hva som ikke er det ikke. Hvis du gjør dette, og finner ut at den lille overhead vektor ikke fungerer for deg, kan du reimplement at klassen uten hjelp av vektor og din klient koden skal ikke trenge å endre.

Svarte 27/03/2009 kl. 04:45
kilden bruker

stemmer
2

Hvis du virkelig gjør C ++, er det ingen praktisk forskjell mellom en klasse og en struct unntatt standard medlem synlighet - klassene har privat synlighet som standard mens structs har offentlig synlighet som standard. Følgende er likeverdige:

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

Poenget er at du ikke trenger den CreatePacket (). Du kan ganske enkelt initialisere struct objekt med en konstruktør.

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

Et par ting å merke seg. I C ++, bruke nye stedet for malloc. Jeg har tatt noen frihet og endret bitlength til bytelength. Hvis denne klassen representerer en nettverkspakke, vil du være mye bedre å håndtere bytes i stedet for biter (etter min mening). Dataene matrise er en matrise av unsigned char, ikke usignert int. Igjen, dette er basert på min antakelse om at denne klassen representerer en nettverkspakke. Konstruktøren kan du lage en pakke som dette:

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

Destructor kalles automatisk når Packet eksempel går ut av rammen og hindrer en minnelekkasje.

Svarte 27/03/2009 kl. 04:02
kilden bruker

stemmer
1

Du har sannsynligvis vil ha noe lettere enn en vektor for høy ytelse. Du ønsker også å være veldig konkret om størrelsen på pakken for å være på tvers av plattformer. Men du ønsker ikke å bry seg om minnelekkasjer heller.

Heldigvis boost bibliotek gjorde det meste av den vanskelige delen:

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));
}
Svarte 27/03/2009 kl. 10:44
kilden bruker

stemmer
1

Det finnes allerede mange gode tanker som er nevnt her. Men en mangler. Fleksible Arrays er en del av C99 og dermed er ikke en del av C ++, selv om noen C ++ kompilatoren kan gi denne funksjonaliteten er det ingen garanti for det. Hvis du finner en måte å bruke dem i C ++ på en akseptabel måte, men du har en kompilator som ikke støtter det, du kanskje kan fallback til "klassisk" måte

Svarte 27/03/2009 kl. 09:22
kilden bruker

stemmer
0

Det er ingenting overhodet galt med å bruke vektor for matriser av ukjent størrelse som vil bli løst etter initialisering. IMHO, det er akkurat hva vektorer er for. Når du har det initialisert, kan du late som ting er en matrise, og det bør oppføre seg på samme (inkludert tid atferd).

Svarte 27/03/2009 kl. 20:23
kilden bruker

stemmer
0

Du bør erklære en peker, ikke en matrise med en uspesifisert lengde.

Svarte 27/03/2009 kl. 04:10
kilden bruker

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more