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&#8217aimerais éviter car l&#8217ensemble des données pourrait être très grand.

Commentaires sur la question (11)

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 de std::string_view, et alors que std::span et std::string_view sont des vues non propriétaires, std::string_view est une vue en lecture seule.

D'après la documentation :

Le modèle de classe span décrit un objet qui peut se référer à un séquence contiguë d'objets avec le premier élément de la séquence à la position zéro. Une portée peut soit avoir une étendue statique, dans laquelle si le nombre d'éléments dans la séquence est connu et encodé dans le type, ou une étendue dynamique.

Donc, la méthode suivante fonctionnerait :


#inclure <span>
#inclure 
#inclure 

int main() {
    int data[] = { 5, 3, 2, 1, 4 } ;
    std::span s{data, 5} ;

    std::sort(s.begin(), s.end()) ;

    for (auto const i : s) {
        std::cout 
Commentaires (2)
Solution

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 pour std::string). Vous pouvez écrire votre propre implémentation de modèle de classe array_view dont les instances sont construites en prenant un pointeur brut vers le premier élément d'un tableau et la longueur du tableau :

#include 

template
class array_view {
   T* ptr_;
   std::size_t len_;
public:
   array_view(T* ptr, std::size_t len) noexcept: ptr_{ptr}, len_{len} {}

   T& operator[](int i) noexcept { return ptr_[i]; }
   T const& operator[](int i) const noexcept { return ptr_[i]; }
   auto size() const noexcept { return len_; }

   auto begin() noexcept { return ptr_; }
   auto end() noexcept { return ptr_ + len_; }
};

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 objets array_view sont peu coûteux à construire et à copier.

Puisque array_view fournit les fonctions membres begin() et end(), vous pouvez utiliser les algorithmes standards de la bibliothèque (par exemple, std::sort, std::find, std::lower_bound, etc :


#define LEN 5

auto main() -> int {
   int arr[LEN] = {4, 5, 1, 2, 3};

   array_view av(arr, LEN);

   std::sort(av.begin(), av.end());

   for (auto const& val: av)
      std::cout 
Commentaires (2)

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...) :


#include 
#include 

int *get_data_from_library(int &size) {
    static int data[] = {5,3,2,1,4} ;

    taille = 5 ;

    retourne les données ;
}

int main()
{
    int taille ;
    int *data = get_data_from_library(size) ;

    std::sort(données, données + taille) ;

    for (int i = 0 ; i < size ; i++)
    {
        std::cout 
Commentaires (1)

Vous pouvez obtenir des itérateurs sur des tableaux bruts et les utiliser dans des algorithmes :


    int data[] = {5,3,2,1,4} ;
    std::sort(std::begin(data), std::end(data)) ;
    for (auto i : données) {
        std::cout 
Commentaires (4)

J'aimerais maintenant utiliser std::vector pour accéder à ces valeurs et les modifier en place

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 ...

, car je dois appliquer des algorithmes de (tri, échange d'éléments, etc.) sur ces données.

Ces algorithmes fonctionnent sur des itérateurs. Un pointeur est un itérateur d'un tableau. Vous n'avez pas besoin d'un vecteur :

std::sort(data, data + size);

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`.

Commentaires (0)

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&quot ; 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

Commentaires (0)

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.

Commentaires (3)

En plus de l'autre bonne suggestion concernant l'arrivée de std::span [tag:c++20] et gsl:span, inclure votre propre classe span (légère) jusqu'à ce moment est déjà assez facile (n'hésitez pas à copier) :


template
struct span {
    T* first;
    size_t length;
    span(T* first_, size_t length_) : first(first_), length(length_) {};
    using value_type = std::remove_cv_t;//primarily needed if used with templates
    bool empty() const { return length == 0; }
    auto begin() const { return first; }
    auto end() const { return first + length; }
};

static_assert(_MSVC_LANG 
Commentaires (6)

Vous pourriez utiliser un [std::reference_wrapper][1] disponible depuis C++11 :


#include 
#include 
#include 
#include 

int main()
{
    int src_table[] = {5, 4, 3, 2, 1, 0};

    std::vector< std::reference_wrapper< int > > dest_vector;

    std::copy(std::begin(src_table), std::end(src_table), std::back_inserter(dest_vector));
    // if you don't have the array defined just a pointer and size then:
    // std::copy(src_table_ptr, src_table_ptr + size, std::back_inserter(dest_vector));

    std::sort(std::begin(dest_vector), std::end(dest_vector));

    std::for_each(std::begin(src_table), std::end(src_table), [](int x) { std::cout 
Commentaires (10)

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 :

span lite - Un span de type C++20 pour le C++98, le C++11 et plus tard dans une bibliothèque d'en-tête à fichier unique

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 :


#include 
#include 
#include 

#include 

static int data[] = {5, 1, 2, 4, 3};

// For example
int* get_data_from_library()
{
  return data;
}

int main ()
{
  const std::size_t size = 5;

  nonstd::span v{get_data_from_library(), size};

  std::sort(v.begin(), v.end());

  for (auto i = 0UL; i < v.size(); ++i)
  {
    std::cout 
Commentaires (0)