Utilisation de std::vector comme vue sur la mémoire brute
J'utilise une bibliothèque externe qui, à un moment donné, me donne un pointeur brut vers un tableau d'entiers et une taille.
J'aimerais maintenant utiliser std::vector
pour accéder à ces valeurs et les modifier sur place, plutôt que d'y accéder avec des pointeurs bruts.
Voici un exemple articifiel qui explique ce point :
size_t size = 0;
int * data = get_data_from_library(size); // raw data from library {5,3,2,1,4}, size gets filled in
std::vector<int> v = ????; // pseudo vector to be used to access the raw data
std::sort(v.begin(), v.end()); // sort raw data in place
for (int i = 0; i < 5; i++)
{
std::cout << data[i] << "\n"; // display sorted raw data
}
Sortie attendue :
1
2
3
4
5
La raison est que j'ai besoin d'appliquer des algorithmes de <algorithm>
(tri, échange d'éléments etc.) sur ces données.
D'un autre côté, la taille de ce vecteur ne sera jamais modifiée, donc push_back
, erase
, insert
ne sont pas nécessaires pour travailler sur ce vecteur.
Je pourrais construire un vecteur basé sur les données de la bibliothèque, utiliser la modification de ce vecteur et recopier les données dans la bibliothèque, mais cela représenterait deux copies complètes que j’aimerais éviter car l’ensemble des données pourrait être très grand.
C++20's
std::span
(en anglais)Si vous pouvez utiliser le C++20, vous pouvez utiliser
std::span
qui est une paire pointeur-longueur qui donne à l'utilisateur une vue d'une séquence contiguë d'éléments. C'est une sorte destd::string_view
, et alors questd::span
etstd::string_view
sont des vues non propriétaires,std::string_view
est une vue en lecture seule.D'après la documentation :
Donc, la méthode suivante fonctionnerait :
Le problème est que
std::vector
doit faire une copie des éléments du tableau avec lequel vous l'initialisez car il a la propriété des objets qu'il contient.Pour éviter cela, vous pouvez utiliser un objet slice pour un tableau (c'est-à-dire similaire à ce que
std::string_view
est pourstd::string
). Vous pouvez écrire votre propre implémentation de modèle de classearray_view
dont les instances sont construites en prenant un pointeur brut vers le premier élément d'un tableau et la longueur du tableau :array_view
ne stocke pas un tableau ; il contient juste un pointeur vers le début du tableau et la longueur de ce tableau. Par conséquent, les objetsarray_view
sont peu coûteux à construire et à copier.Puisque
array_view
fournit les fonctions membresbegin()
etend()
, vous pouvez utiliser les algorithmes standards de la bibliothèque (par exemple,std::sort
,std::find
,std::lower_bound
, etc :Comme la bibliothèque d'algorithmes fonctionne avec des itérateurs, vous pouvez conserver le tableau.
Pour les pointeurs et une longueur de tableau connue
Ici, vous pouvez utiliser des pointeurs bruts comme itérateurs. Ils supportent toutes les opérations qu'un itérateur supporte (incrément, comparaison pour l'égalité, valeur de, etc...) :
Vous pouvez obtenir des itérateurs sur des tableaux bruts et les utiliser dans des algorithmes :
Vous ne pouvez pas. Ce n'est pas à cela que sert
std::vector
.std::vector
gère son propre tampon, qui est toujours acquis à partir d'un allocateur. Il ne prend jamais possession d'un autre tampon (sauf d'un autre vecteur du même type).D'autre part, vous n'avez pas besoin de le faire parce que ...
Ces algorithmes fonctionnent sur des itérateurs. Un pointeur est un itérateur d'un tableau. Vous n'avez pas besoin d'un vecteur :
Contrairement aux modèles de fonctions dans `
, certains outils tels que range-for,
std::begin/
std::endet les plages C++20 ne fonctionnent pas avec une simple paire d'itérateurs cependant, alors qu'ils fonctionnent avec des conteneurs tels que des vecteurs. Il est possible de créer une classe enveloppe pour l'itérateur + taille qui se comporte comme une plage, et fonctionne avec ces outils. Le C++20 introduira une telle classe d'enveloppes dans la bibliothèque standard :
std::span`.Vous ne pouvez pas le faire avec un
std::vector
sans en faire une copie.std::vector
possède le pointeur qu'il a sous le capot et alloue de l'espace via l'allocateur qui est fourni.Si vous avez accès à un compilateur qui supporte C++20, vous pouvez utiliser [std::span][1] qui a été construit exactement dans ce but. Il encapsule un pointeur et une taille dans un "container" ; qui possède l'interface de container C++.
Si ce n'est pas le cas, vous pouvez utiliser [gsl::span][2] qui est la base de la version standard.
Si vous ne voulez pas importer une autre bibliothèque, vous pouvez trivialement l'implémenter vous-même en fonction de toutes les fonctionnalités que vous souhaitez avoir.
[1] : https://en.cppreference.com/w/cpp/container/span [2] : https://github.com/microsoft/GSL/blob/master/include/gsl/span
En fait, vous pourriez presque utiliser
std::vector
pour cela, en abusant de la fonctionnalité d'allocation personnalisée pour renvoyer un pointeur vers la mémoire que vous souhaitez visualiser. Le standard ne garantit pas que cela fonctionne (rembourrage, alignement, initialisation des valeurs retournées ; il faudrait se donner du mal pour attribuer la taille initiale, et pour les non privilégiés, il faudrait aussi pirater les constructeurs), mais en pratique, je m'attendrais à ce qu'il y ait suffisamment de modifications.Ne faites jamais cela. C'est moche, surprenant, hacky et inutile. Les algorithmes de la bibliothèque standard sont déjà conçus pour fonctionner aussi bien avec des tableaux bruts qu'avec des vecteurs. Voir les autres réponses pour plus de détails à ce sujet.
En plus de l'autre bonne suggestion concernant l'arrivée de
std::span
[tag:c++20] etgsl:span
, inclure votre propre classespan
(légère) jusqu'à ce moment est déjà assez facile (n'hésitez pas à copier) :Vous pourriez utiliser un [
std::reference_wrapper
][1] disponible depuis C++11 :Comme d'autres l'ont fait remarquer,
std::vector
doit posséder la mémoire sous-jacente (à moins de jouer avec un allocateur personnalisé) et ne peut donc pas être utilisé.D'autres ont également recommandé la portée de c++20, mais il est évident que cela nécessite c++20.
Je recommande la portée [span-lite][1]. Pour citer son sous-titre :
Il fournit une vue non propriétaire et mutable (comme dans vous pouvez muter des éléments et leur ordre mais pas les insérer) et comme le dit la citation, n'a pas de dépendances et fonctionne sur la plupart des compilateurs.
Votre exemple :