Comment copier le contenu d'un tableau dans un std :: vector en C ++ sans boucle?

123

J'ai un tableau de valeurs qui est passé à ma fonction à partir d'une partie différente du programme que je dois stocker pour un traitement ultérieur. Comme je ne sais pas combien de fois ma fonction sera appelée avant qu'il ne soit temps de traiter les données, j'ai besoin d'une structure de stockage dynamique, j'ai donc choisi un fichier std::vector. Je ne veux pas avoir à faire la boucle standard pour push_backtoutes les valeurs individuellement, ce serait bien si je pouvais simplement tout copier en utilisant quelque chose de similaire à memcpy.

bsruth
la source

Réponses:

117

Si vous pouvez construire le vecteur après avoir obtenu le tableau et la taille du tableau, vous pouvez simplement dire:

std::vector<ValueType> vec(a, a + n);

... en supposant que avotre tableau et nle nombre d'éléments qu'il contient. Sinon, std::copy()w / resize()fera l'affaire.

Je resterais à l'écart, memcpy()sauf si vous pouvez être sûr que les valeurs sont des types de données anciennes (POD).

De plus, il convient de noter qu'aucun de ceux-ci n'évite vraiment la boucle for - il s'agit simplement de savoir si vous devez le voir dans votre code ou non. Les performances d'exécution O (n) sont inévitables pour la copie des valeurs.

Enfin, notez que les tableaux de style C sont des conteneurs parfaitement valides pour la plupart des algorithmes STL - le pointeur brut est équivalent à begin(), et ( ptr + n) est équivalent à end().

Drew Hall
la source
4
La raison pour laquelle la boucle et l'appel de push_back sont mauvais est que vous pouvez forcer le vecteur à se redimensionner plusieurs fois si le tableau est suffisamment long.
bradtgmurray
@bradtgmurray: Je pense que toute implémentation raisonnable du constructeur vectoriel "deux itérateurs" que j'ai suggéré ci-dessus appellerait std :: distance () d'abord sur les deux itérateurs pour obtenir le nombre d'éléments nécessaires, puis allouer une seule fois.
Drew Hall
4
@bradtgmurray: Même push_back () ne serait pas trop mal à cause de la croissance exponentielle des vecteurs (aka "temps constant amorti"). Je pense que l'exécution ne serait que de l'ordre de 2x pire dans le pire des cas.
Drew Hall
2
Et si le vecteur est déjà là, un vec.clear (); vec.insert (vec.begin (), a, a + n); fonctionnerait également. Ensuite, vous n'auriez même pas besoin de a pour être un pointeur, juste un itérateur, et l'affectation de vecteur serait un échec général (et la manière C ++ / STL).
MP24
6
Une autre alternative lorsqu'il est incapable de construire serait assign : vec.assign(a, a+n), ce qui serait plus compact que copie et Redimensionner.
mMontu
210

Il y a eu de nombreuses réponses ici et à peu près toutes feront le travail.

Cependant, il y a quelques conseils trompeurs!

Voici les options:

vector<int> dataVec;

int dataArray[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
unsigned dataArraySize = sizeof(dataArray) / sizeof(int);

// Method 1: Copy the array to the vector using back_inserter.
{
    copy(&dataArray[0], &dataArray[dataArraySize], back_inserter(dataVec));
}

// Method 2: Same as 1 but pre-extend the vector by the size of the array using reserve
{
    dataVec.reserve(dataVec.size() + dataArraySize);
    copy(&dataArray[0], &dataArray[dataArraySize], back_inserter(dataVec));
}

// Method 3: Memcpy
{
    dataVec.resize(dataVec.size() + dataArraySize);
    memcpy(&dataVec[dataVec.size() - dataArraySize], &dataArray[0], dataArraySize * sizeof(int));
}

// Method 4: vector::insert
{
    dataVec.insert(dataVec.end(), &dataArray[0], &dataArray[dataArraySize]);
}

// Method 5: vector + vector
{
    vector<int> dataVec2(&dataArray[0], &dataArray[dataArraySize]);
    dataVec.insert(dataVec.end(), dataVec2.begin(), dataVec2.end());
}

Pour faire court, la méthode 4, en utilisant vector :: insert, est la meilleure pour le scénario de bsruth.

Voici quelques détails sanglants:

La méthode 1 est probablement la plus simple à comprendre. Copiez simplement chaque élément du tableau et poussez-le à l'arrière du vecteur. Hélas, c'est lent. Puisqu'il y a une boucle (implicite avec la fonction de copie), chaque élément doit être traité individuellement; aucune amélioration des performances ne peut être apportée sur la base du fait que nous savons que le tableau et les vecteurs sont des blocs contigus.

La méthode 2 est une amélioration des performances suggérée par rapport à la méthode 1; juste pré-réserver la taille du tableau avant de l'ajouter. Pour les grands tableaux, cela peut aider. Cependant, le meilleur conseil ici est de ne jamais utiliser la réserve à moins que le profilage ne suggère que vous puissiez obtenir une amélioration (ou vous devez vous assurer que vos itérateurs ne seront pas invalides). Bjarne est d'accord . Incidemment, j'ai trouvé que cette méthode était la plus lente la plupart du temps, même si j'ai du mal à expliquer en détail pourquoi elle était régulièrement beaucoup plus lente que la méthode 1 ...

La méthode 3 est la solution à l'ancienne - jetez un peu de C sur le problème! Fonctionne très bien et rapidement pour les types POD. Dans ce cas, le redimensionnement doit être appelé car memcpy fonctionne en dehors des limites du vecteur et il n'y a aucun moyen de dire à un vecteur que sa taille a changé. En plus d'être une solution moche (copie d'octets!), Rappelez-vous que cela ne peut être utilisé que pour les types POD . Je n'utiliserais jamais cette solution.

La méthode 4 est la meilleure solution. Sa signification est claire, c'est (généralement) le plus rapide et cela fonctionne pour tous les objets. Il n'y a aucun inconvénient à utiliser cette méthode pour cette application.

La méthode 5 est une modification de la méthode 4 - copiez le tableau dans un vecteur, puis ajoutez-le. Bonne option - généralement rapide et claire.

Enfin, vous savez que vous pouvez utiliser des vecteurs à la place des tableaux, non? Même lorsqu'une fonction attend des tableaux de style C, vous pouvez utiliser des vecteurs:

vector<char> v(50); // Ensure there's enough space
strcpy(&v[0], "prefer vectors to c arrays");

J'espère que cela aide quelqu'un là-bas!

MattyT
la source
6
Vous ne pouvez pas faire référence en toute sécurité et de manière portable à "& dataArray [dataArraySize]" - il déréférencer un pointeur / itérateur au-delà de la fin. Au lieu de cela, vous pouvez dire dataArray + dataArraySize pour obtenir le pointeur sans avoir à le déréférencer au préalable.
Drew Hall
2
@Drew: oui, vous pouvez, au moins en C. Il est défini qui &exprn'évalue pas expr, il ne calcule que l'adresse de celui-ci. Et un pointeur un après le dernier élément est tout à fait valable, aussi.
Roland Illig
2
Avez-vous essayé de faire la méthode 4 avec 2? c'est-à-dire réserver l'espace avant l'insertion. Il semble que si la taille des données est grande, plusieurs insertions nécessiteront plusieurs réallocations. Parce que nous connaissons la taille a priori, nous pouvons faire la réallocation, avant l'insertion.
Jorge Leitao
2
@MattyT quel est l'intérêt de la méthode 5? Pourquoi faire une copie intermédiaire des données?
Ruslan
2
Personnellement, je préférerais profiter des tableaux qui se désintègrent automatiquement en pointeurs: dataVec.insert(dataVec.end(), dataArray, dataArray + dataArraySize);- me semble beaucoup plus clair. Ne peut rien gagner de la méthode 5 non plus, cela semble assez inefficace - à moins que le compilateur ne soit capable d'optimiser à nouveau le vecteur.
Aconcagua le
37

Si vous ne faites que remplacer les données existantes, vous pouvez le faire

std::vector<int> data; // evil global :)

void CopyData(int *newData, size_t count)
{
   data.assign(newData, newData + count);
}
Torlack
la source
1
Simple à comprendre et certainement la solution la plus rapide (c'est juste un memcpy dans les coulisses).
Don Scott
Deta.assign est-il plus rapide que data.insert?
Jim
11

std :: copy est ce que vous recherchez.

Luke
la source
10

Comme je ne peux modifier que ma propre réponse, je vais faire une réponse composite à partir des autres réponses à ma question. Merci à tous ceux qui ont répondu.

En utilisant std :: copy , cela fonctionne toujours en arrière-plan, mais vous n'avez pas à taper le code.

int foo(int* data, int size)
{
   static std::vector<int> my_data; //normally a class variable
   std::copy(data, data + size, std::back_inserter(my_data));
   return 0;
}

Utilisation de memcpy ordinaire . Ceci est probablement mieux utilisé pour les types de données de base (ie int) mais pas pour les tableaux plus complexes de structures ou de classes.

vector<int> x(size);
memcpy(&x[0], source, size*sizeof(int));
bsruth
la source
J'allais recommander cette approche.
mmocny
Il est probablement plus efficace de redimensionner votre vecteur à l'avance si vous connaissez la taille à l'avance et que vous n'utilisez pas le back_inserter.
luke
vous pouvez ajouter my_data.reserve (taille)
David Nehme
Notez qu'en interne, cela fait exactement ce que vous semblez vouloir éviter. Ce n'est pas une copie de bits, c'est juste une boucle et un appel push_back (). Je suppose que vous vouliez seulement éviter de taper le code?
mmocny
1
Pourquoi ne pas utiliser le constructeur vectoriel pour copier les données?
Martin York
3

éviter le memcpy, dis-je. Aucune raison de jouer avec les opérations de pointeur à moins que vous n'ayez vraiment à le faire. De plus, cela ne fonctionnera que pour les types POD (comme int) mais échouera si vous avez affaire à des types qui nécessitent une construction.

Assaf Lavie
la source
8
Peut-être que cela devrait être un commentaire sur l'une des autres réponses, car vous ne proposez pas réellement de solution.
fin
3
int dataArray[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };//source

unsigned dataArraySize = sizeof(dataArray) / sizeof(int);

std::vector<int> myvector (dataArraySize );//target

std::copy ( myints, myints+dataArraySize , myvector.begin() );

//myvector now has 1,2,3,...10 :-)
Antonio Ramasco
la source
2
Bien que cet extrait de code soit le bienvenu et puisse fournir une aide, il serait grandement amélioré s'il incluait une explication sur la manière et la raison pour laquelle cela résout le problème. N'oubliez pas que vous répondez à la question des lecteurs à l'avenir, pas seulement à la personne qui la pose maintenant! Veuillez modifier votre réponse pour ajouter une explication et donner une indication des limites et des hypothèses applicables.
Toby Speight
4
Attends quoi myints?
mavavilj
2

Encore une autre réponse, puisque la personne a dit "Je ne sais pas combien de fois ma fonction sera appelée", vous pouvez utiliser la méthode d'insertion vectorielle comme ceci pour ajouter des tableaux de valeurs à la fin du vecteur:

vector<int> x;

void AddValues(int* values, size_t size)
{
   x.insert(x.end(), values, values+size);
}

J'aime cette façon parce que l'implémentation du vecteur devrait être en mesure d'optimiser la meilleure façon d'insérer les valeurs en fonction du type d'itérateur et du type lui-même. Vous répondez quelque peu sur l'implémentation de stl.

Si vous avez besoin de garantir la vitesse la plus rapide et que vous savez que votre type est un type POD, je recommanderais la méthode de redimensionnement dans la réponse de Thomas:

vector<int> x;

void AddValues(int* values, size_t size)
{
   size_t old_size(x.size());
   x.resize(old_size + size, 0);
   memcpy(&x[old_size], values, size * sizeof(int));
}
Shane Powell
la source
1

En plus des méthodes présentées ci-dessus, vous devez vous assurer d'utiliser soit std :: Vector.reserve (), std :: Vector.resize (), ou construire le vecteur à la taille, pour vous assurer que votre vecteur a suffisamment d'éléments dans pour conserver vos données. sinon, vous corromprez la mémoire. Ceci est vrai pour std :: copy () ou memcpy ().

C'est la raison pour laquelle vous utilisez vector.push_back (), vous ne pouvez pas écrire au-delà de la fin du vecteur.

Thomas Jones-Low
la source
Si vous utilisez un back_inserter, vous n'avez pas besoin de pré-réserver la taille du vecteur dans lequel vous copiez. back_inserter fait un push_back ().
John Dibling
0

En supposant que vous connaissiez la taille de l'élément dans le vecteur:

std::vector<int> myArray;
myArray.resize (item_count, 0);
memcpy (&myArray.front(), source, item_count * sizeof(int));

http://www.cppreference.com/wiki/stl/vector/start

Thomas Jones-Low
la source
Cela ne dépend-il pas de l'implémentation de std :: vector?
ReaperUnreal
C'est horrible! Vous remplissez le tableau deux fois, l'un avec des «0», puis avec les valeurs appropriées. Faites simplement: std :: vector <int> myArray (source, source + item_count); et faites confiance à votre compilateur pour produire le memcpy!
Chris Jefferson
Faites confiance à votre compilateur pour produire __memcpy_int_aligned; cela devrait être encore plus rapide
MSalters