web-dev-qa-db-fra.com

Un type de tampon dynamique en C++?

Je ne suis pas vraiment un débutant en C++, mais j’ai eu peu de relations sérieuses avec cela dans le passé, donc ma connaissance de ses installations est plutôt sommaire.

J'écris un programme de validation de concept rapide en C++ et j'ai besoin d'un tampon de données binaires de taille dynamique. C'est-à-dire que je vais recevoir des données d'une prise réseau et je ne sais pas combien il y en aura (mais pas plus de quelques Mo). Je pourrais écrire un tel tampon moi-même, mais pourquoi ne pas déranger si la bibliothèque standard a probablement déjà quelque chose? J'utilise VS2008, donc une extension spécifique à Microsoft me convient parfaitement. Je n'ai besoin que de quatre opérations:

  • Créer le tampon
  • Ecrire des données dans le tampon (binaire indésirable, pas terminé par zéro)
  • Récupère les données écrites sous forme de tableau de caractères (avec sa longueur)
  • Libérer le tampon

Quel est le nom de la classe/fonction/quel que soit ce dont j'ai besoin?

Ajouté: Plusieurs votes vont à std::vector. Tout va bien, mais je ne veux pas pousser plusieurs Mo de données octet par octet. Le socket me fournira des données en gros morceaux de quelques ko. J'aimerais donc les écrire tous en même temps. De plus, à la fin, j'aurai besoin de récupérer les données sous forme d'un simple caractère *, car je devrai transmettre l'intégralité du blob à certaines fonctions de l'API Win32 non modifiées.

23
Vilx-

Vous voulez un std::vector :

std::vector<char> myData;

vector allouera et désallouera automatiquement sa mémoire pour vous. Utilisez Push_back pour ajouter de nouvelles données (vector se redimensionnera si nécessaire) et l'opérateur d'indexation [] pour récupérer les données.

Si, à un moment quelconque, vous pouvez deviner la quantité de mémoire dont vous aurez besoin, je suggère d'appeler reserve afin que les Push_back suivants ne soient pas obligés de redistribuer autant.

Si vous voulez lire une partie de la mémoire et l'ajouter à votre tampon, le plus simple serait probablement quelque chose comme:

std::vector<char> myData;
for (;;) {
    const int BufferSize = 1024;
    char rawBuffer[BufferSize];

    const unsigned bytesRead = get_network_data(rawBuffer, sizeof(rawBuffer));
    if (bytesRead <= 0) {
        break;
    }

    myData.insert(myData.end(), rawBuffer, rawBuffer + bytesRead);
}

myData a maintenant toutes les données lues, lisant morceau par morceau. Cependant, nous copions deux fois.

Nous essayons plutôt quelque chose comme ceci:

std::vector<char> myData;
for (;;) {
    const int BufferSize = 1024;

    const size_t oldSize = myData.size();
    myData.resize(myData.size() + BufferSize);        

    const unsigned bytesRead = get_network_data(&myData[oldSize], BufferSize);
    myData.resize(oldSize + bytesRead);

    if (bytesRead == 0) {
        break;
    }
}

Ce qui se lit directement dans la mémoire tampon, au prix d’une surallocation occasionnelle.

Cela peut être rendu plus intelligent, par exemple. doubler la taille du vecteur pour chaque redimensionnement afin d'amortir les redimensionnements, comme le fait implicitement la première solution. Et bien sûr, vous pouvez reserve() un tampon beaucoup plus grand à l’avance si vous connaissez a priori la taille probable du tampon final, afin de minimiser les redimensionnements.

Les deux sont laissés comme un exercice pour le lecteur. :)

Enfin, si vous devez traiter vos données comme un tableau brut:

some_c_function(myData.data(), myData.size());

std::vector est garanti d'être contigu.

39
GManNickG
std::vector<unsigned char> buffer;

Chaque Push_back ajoutera un nouveau caractère à la fin (réallocation si nécessaire). Vous pouvez appeler réserve pour réduire le nombre d’allocations si vous savez approximativement le volume de données que vous attendez.

buffer.reserve(1000000);

Si vous avez quelque chose comme ça:

unsigned char buffer[1000];
std::vector<unsigned char> vec(buffer, buffer + 1000);
9
Nikola Smiljanić

std::string fonctionnerait pour ceci:

  • Il prend en charge les valeurs null intégrées.
  • Vous pouvez y ajouter des fragments de données multi-octets en appelant append() avec un pointeur et une longueur.
  • Vous pouvez obtenir son contenu sous forme de tableau de caractères en appelant data() et la longueur actuelle en appelant size() ou length().
  • La libération du tampon est gérée automatiquement par le destructeur, mais vous pouvez également appeler clear() pour effacer son contenu sans le détruire.
7
Wyzard

Encore un vote pour std :: vector. Code minimal, ignore la copie supplémentaire du code de GMan:

std::vector<char> buffer;
static const size_t MaxBytesPerRecv = 1024;
size_t bytesRead;
do
{
    const size_t oldSize = buffer.size();

    buffer.resize(oldSize + MaxBytesPerRecv);
    bytesRead = receive(&buffer[oldSize], MaxBytesPerRecv); // pseudo, as is the case with winsock recv() functions, they get a buffer and maximum bytes to write to the buffer

    myData.resize(oldSize + bytesRead); // shrink the vector, this is practically no-op - it only modifies the internal size, no data is moved/freed
} while (bytesRead > 0);

En ce qui concerne l’appel de fonctions WinAPI - utilisez & buffer [0] (oui, c’est un peu maladroit, mais c’est comme ça) pour passer aux arguments char *, buffer.size () comme longueur. 

Et une note finale, vous pouvez utiliser std :: string au lieu de std :: vector, il ne devrait y avoir aucune différence (sauf que vous pouvez écrire buffer.data () au lieu de & buffer [0] si votre tampon est une chaîne)

6
sbk

Je voudrais jeter un oeil à Boost basic_streambuf , qui est conçu pour ce genre de but. Si vous ne pouvez pas (ou ne voulez pas) utiliser Boost, je considérerais std::basic_streambuf, qui est assez similaire, mais qui demande un peu plus de travail. Quoi qu'il en soit, vous dérivez essentiellement de cette classe de base et surchargez underflow() pour lire les données du socket dans la mémoire tampon. Normalement, vous attacherez un std::istream à la mémoire tampon, de sorte que les autres codes se liront de la même manière que les entrées utilisateur au clavier (ou peu importe).

4
Jerry Coffin

Une alternative qui ne provient pas de STL mais qui pourrait être utile - Boost.Circular buffer

2
Sergei Kurenkov

Utilisez std :: vector , un tableau en pleine croissance garantissant que le stockage est contigu (votre troisième point).

1
Xavier Nodet

En ce qui concerne votre commentaire "Je ne vois pas un append ()", ineserting à la fin est la même chose.

vec.insert (vec.end,

0
Brian D. Coryell

Si vous utilisez std :: vector, vous ne l'utilisez que pour gérer la mémoire brute pour vous. Vous pouvez simplement malloc le plus grand tampon dont vous pensez avoir besoin, et garder une trace du décalage d'écriture/nombre total d'octets lus jusqu'à présent (c'est la même chose). Si vous arrivez à la fin ... soit realloc ou choisissez un moyen d'échouer.

Je sais, ce n’est pas très C++ y, mais c’est un problème simple et les autres propositions semblent être des moyens assez lourds d’introduire une copie inutile.

0
Useless