Structure de taille variable C ++

Est-ce la meilleure façon de créer une structure de taille variable en C ++? Je ne veux pas utiliser de vecteur car la longueur ne change pas après l’initialisation.

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: les noms de variables renommés et le code modifié pour être plus correct.

Quelques reflections sur ce que vous faites:

  • L’utilisation de la structure de longueur variable de style C vous permet d’effectuer une allocation de magasin libre par paquet, soit la moitié du nombre requirejs si struct Packet contenait un std::vector . Si vous allouez un très grand nombre de paquets, le nombre d’allocations / désallocations de magasins libres équivalent à la moitié de ce nombre pourrait bien s’avérer important. Si vous effectuez également des access réseau, le temps d’attente du réseau sera probablement plus important.

  • Cette structure représente un paquet. Envisagez-vous de lire / écrire à partir d’un socket directement dans un struct Packet ? Si tel est le cas, vous devez probablement tenir compte de l’ordre des octets. Allez-vous devoir convertir l’ordre des octets en hôte en réseau lors de l’envoi de paquets, et inversement lors de la réception de paquets? Si tel est le cas, vous pouvez échanger les données sur place dans votre structure de longueur variable. Si vous convertissez cela en vecteur, il serait judicieux d’écrire des méthodes pour sérialiser / désérialiser le paquet. Ces méthodes le transféreraient vers / depuis un tampon contigu, en tenant compte de l’ordre des octets.

  • De même, vous devrez peut-être prendre en compte l’alignement et l’emballage.

  • Vous ne pouvez jamais sous- Packet . Si vous le faites, les variables membres de la sous-classe chevaucheront le tableau.

  • Au lieu de malloc et free , vous pouvez utiliser Packet* p = ::operator new(size) et ::operator delete(p) , car struct Packet est un type POD et ne tire actuellement aucun avantage à ce que son constructeur par défaut et son destructeur soient appelés. . L’avantage (potentiel) est que l’ operator new global operator new gère les erreurs à l’aide du gestionnaire global et / ou des exceptions, si cela vous importe.

  • Il est possible de faire fonctionner le langage de structure de longueur variable avec les nouveaux opérateurs et les opérateurs de suppression, mais pas correctement. Vous pouvez créer un operator new personnalisé operator new qui prend une longueur de tableau en implémentant static void* operator new(size_t size, unsigned int bitlength) , mais vous devez tout de même définir la variable membre bitlength. Si vous faisiez cela avec un constructeur, vous pourriez utiliser l’expression légèrement redondante Packet* p = new(len) Packet(len) pour allouer un paquet. Le seul avantage que je vois par rapport à l’utilisation de l’ operator new global operator new et de l’ operator delete serait que les clients de votre code pourraient simplement appeler delete p au lieu de ::operator delete(p) . Envelopper l’allocation / désallocation dans des fonctions séparées (au lieu d’appeler delete p directement) est acceptable tant qu’elles sont appelées correctement.

Si vous n’ajoutez jamais de constructeur / destructeur, d’opérateurs d’affectation ou de fonctions virtuelles à votre structure en utilisant malloc / free pour l’atsortingbution est sans danger.

C’est mal vu dans les cercles c ++, mais je considère que son utilisation est acceptable si vous la documentez dans le code.

Quelques commentaires à votre code:

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

Si je me souviens bien, déclarer un tableau sans longueur n’est pas standard. Cela fonctionne sur la plupart des compilateurs mais peut vous donner un avertissement. Si vous voulez être conforme, déclarez votre tableau de longueur 1.

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

Cela fonctionne, mais vous ne tenez pas compte de la taille de la structure. Le code sera rompu une fois que vous aurez ajouté de nouveaux membres à votre structure. Mieux vaut le faire de cette façon:

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

Et écrivez un commentaire dans votre définition de structure de paquet que les données doivent être le dernier membre.

Btw – allouer la structure et les données avec une seule allocation est une bonne chose. Ainsi, vous divisez par deux le nombre d’allocations et vous améliorez également la localisation des données. Cela peut considérablement améliorer les performances si vous allouez beaucoup de paquets.

Malheureusement, c ++ ne fournit pas un bon mécanisme pour ce faire, vous vous retrouvez souvent avec de telles piratages malloc / free dans des applications du monde réel.

C’est OK (et c’était la pratique standard pour C).

Mais ce n’est pas une bonne idée pour C ++.
En effet, le compilateur génère automatiquement pour vous un ensemble complet d’autres méthodes autour de la classe. Ces méthodes ne comprennent pas que vous avez sortingché.

Par exemple:

 void copyRHSToLeft(Packet& lhs,Packet& rhs) { lhs = rhs; // The comstackr 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); 

Utilisez le std :: vector <> c’est beaucoup plus sûr et fonctionne correctement.
Je parierais également qu’il est tout aussi efficace que votre implémentation après le démarrage de l’optimiseur.

Alternativement, boost contient un tableau de taille fixe:
http://www.boost.org/doc/libs/1_38_0/doc/html/array.html

Vous pouvez utiliser la méthode “C” si vous le souhaitez, mais assurez-vous que le compilateur n’essaiera pas de le copier:

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

Si vous utilisez réellement C ++, il n’y a pas de différence pratique entre une classe et une structure, à l’exception de la visibilité des membres par défaut: les classes ont une visibilité privée par défaut, tandis que les structures ont une visibilité publique par défaut. Les éléments suivants sont équivalents:

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

Le fait est que vous n’avez pas besoin de CreatePacket (). Vous pouvez simplement initialiser l’object struct avec un constructeur.

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

Quelques choses à noter. En C ++, utilisez new au lieu de malloc. J’ai pris un peu de liberté et changé bitlength en bytelength. Si cette classe représente un paquet réseau, vous ferez bien mieux de traiter avec des octets plutôt que des bits (à mon avis). Le tableau de données est un tableau de caractères non signés et non non signés. Encore une fois, ceci est basé sur mon hypothèse que cette classe représente un paquet réseau. Le constructeur vous permet de créer un paquet comme ceci:

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

Le destructeur est appelé automatiquement lorsque l’instance de Packet sort du cadre et évite une fuite de mémoire.

Je me contenterais probablement d’utiliser un vector<> moins que la charge supplémentaire minimale (probablement un seul mot supplémentaire ou un pointeur sur votre implémentation) ne pose vraiment un problème. Rien n’indique que vous devez redimensionner () un vecteur une fois qu’il est construit.

Cependant, il y a plusieurs avantages à utiliser le vector<> :

  • il gère déjà correctement la copie, la cession et la destruction – si vous faites le vôtre, vous devez vous assurer de les manipuler correctement
  • tout le support d’iterator est là – encore une fois, vous n’avez pas à rouler le vôtre.
  • tout le monde sait déjà comment l’utiliser

Si vous voulez vraiment empêcher le tableau de grandir une fois construit, vous pouvez envisager d’avoir votre propre classe qui hérite du vector<> privée ou a un membre de vector<> et qui n’expose que via des méthodes qui se limitent aux méthodes vectorielles. du vecteur que vous souhaitez que les clients puissent utiliser. Cela devrait vous aider à démarrer rapidement avec une bonne assurance que les fuites et les autres ne sont pas là. Si vous faites cela et constatez que la petite surcharge de vector ne fonctionne pas pour vous, vous pouvez réimplémenter cette classe sans l’aide de vector et votre code client ne devrait pas avoir besoin de changer.

Vous voulez probablement quelque chose de plus léger qu’un vecteur pour des performances élevées. Vous voulez aussi être très précis sur la taille de votre paquet pour être multi-plateforme. Mais vous ne voulez pas non plus vous soucier des memory leaks.

Heureusement, la bibliothèque de boost a fait l’essentiel de la partie la plus difficile:

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

Il y a déjà beaucoup de bonnes pensées mentionnées ici. Mais il manque un. Les tableaux flexibles font partie de C99 et ne font donc pas partie de C ++. Bien que certains compilateurs C ++ puissent fournir cette fonctionnalité, rien ne la garantit. Si vous trouvez un moyen acceptable de les utiliser en C ++, mais que votre compilateur ne le prend pas en charge, vous pouvez peut-être revenir à la méthode “classique”.

Vous devez déclarer un pointeur, pas un tableau avec une longueur non spécifiée.

Il n’y a rien de mal à utiliser le vecteur pour des tableaux de taille inconnue qui seront corrigés après l’initialisation. IMHO, c’est exactement ce que les vecteurs sont pour. Une fois que vous l’avez initialisé, vous pouvez prétendre que la chose est un tableau et qu’elle devrait se comporter de la même manière (y compris le comportement temporel).